From 0709b3396c39827cfa60e7f2bcf68a1fcf763d85 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Thu, 4 Oct 2018 10:24:36 +0200 Subject: [PATCH] [MIG] web_responsive: Migrate to v12 and refactor This migration includes a full refactoring to make this module more maintainable. Some things that have changed: - Removed external libraries. - Change Less for Scss. - Reduce ES and XML to the minimal required needs. - Implement as much features as possible with just Scss. - Remove copyright from `__init__.py` files. - Trigger the new hotkeys system from Odoo v12 with `Shift+Alt` instead of just `Alt`, and restore some good old hotkeys (`E` for "Edit", `D` for "Discard", and `A` for "Apps menu"). See https://github.com/odoo/odoo/issues/30068 on the matter. - Control panel breadcrumbs are collapsed into a single backwards icon. - Add FA icons to most common buttons in control panel. - Hide text in XS for those buttons, to have a slicker phone experience. - Lots of gifs in the README! --- web_responsive/README.rst | 108 +- web_responsive/__init__.py | 3 - web_responsive/__manifest__.py | 11 +- web_responsive/i18n/es.po | 75 +- web_responsive/i18n/web_responsive.pot | 74 +- web_responsive/models/__init__.py | 5 +- .../{inherited_res_users.py => res_users.py} | 0 web_responsive/readme/DESCRIPTION.rst | 62 +- web_responsive/readme/ROADMAP.rst | 28 +- web_responsive/readme/USAGE.rst | 8 +- web_responsive/static/description/index.html | 95 +- .../static/lib/css/drawer.3.2.2.css | 525 ---- .../static/lib/js/bililiteRange.2.6.js | 765 ------ web_responsive/static/lib/js/drawer.3.2.2.js | 183 -- .../static/lib/js/iscroll-probe.5.2.0.js | 2197 ----------------- .../static/lib/js/jquery.sendkeys.4.js | 57 - .../static/src/css/web_responsive.scss | 427 ++++ .../static/src/js/web_responsive.js | 809 +++--- .../static/src/less/app_drawer.less | 129 - web_responsive/static/src/less/form_view.less | 212 -- web_responsive/static/src/less/main.less | 102 - web_responsive/static/src/less/navbar.less | 195 -- web_responsive/static/src/less/variables.less | 18 - .../static/src/xml/app_drawer_menu_search.xml | 16 - web_responsive/static/src/xml/apps.xml | 58 + web_responsive/static/src/xml/form_view.xml | 173 +- web_responsive/static/src/xml/navbar.xml | 13 +- .../static/tests/js/web_responsive.js | 290 --- web_responsive/tests/__init__.py | 1 - web_responsive/tests/test_ui.py | 15 - web_responsive/views/assets.xml | 37 +- ...rs_form_simple_modif.xml => res_users.xml} | 0 web_responsive/views/web.xml | 293 +-- 33 files changed, 1247 insertions(+), 5737 deletions(-) rename web_responsive/models/{inherited_res_users.py => res_users.py} (100%) delete mode 100644 web_responsive/static/lib/css/drawer.3.2.2.css delete mode 100644 web_responsive/static/lib/js/bililiteRange.2.6.js delete mode 100644 web_responsive/static/lib/js/drawer.3.2.2.js delete mode 100644 web_responsive/static/lib/js/iscroll-probe.5.2.0.js delete mode 100644 web_responsive/static/lib/js/jquery.sendkeys.4.js create mode 100644 web_responsive/static/src/css/web_responsive.scss delete mode 100644 web_responsive/static/src/less/app_drawer.less delete mode 100644 web_responsive/static/src/less/form_view.less delete mode 100644 web_responsive/static/src/less/main.less delete mode 100644 web_responsive/static/src/less/navbar.less delete mode 100644 web_responsive/static/src/less/variables.less delete mode 100644 web_responsive/static/src/xml/app_drawer_menu_search.xml create mode 100644 web_responsive/static/src/xml/apps.xml delete mode 100644 web_responsive/static/tests/js/web_responsive.js delete mode 100644 web_responsive/tests/test_ui.py rename web_responsive/views/{inherited_view_users_form_simple_modif.xml => res_users.xml} (100%) diff --git a/web_responsive/README.rst b/web_responsive/README.rst index 0fb94cd7d..a179c40c4 100644 --- a/web_responsive/README.rst +++ b/web_responsive/README.rst @@ -14,26 +14,74 @@ Web Responsive :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github - :target: https://github.com/OCA/web/tree/11.0/web_responsive + :target: https://github.com/OCA/web/tree/12.0/web_responsive :alt: OCA/web .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/web-11-0/web-11-0-web_responsive + :target: https://translation.odoo-community.org/projects/web-12-0/web-12-0-web_responsive :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/162/11.0 + :target: https://runbot.odoo-community.org/runbot/162/12.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| -This module provides a mobile compliant interface for Odoo Community web. +This module adds responsiveness to web backend. -Features: +Features for all devices: -* New navigation with an App drawer -* Keyboard shortcuts for easier navigation -* Display kanban views for small screens if an action or field One2x -* Set chatter side (Optional per user) -* Quick search +* New navigation with an app drawer + + .. image:: https://user-images.githubusercontent.com/973709/48417193-09a1e080-e74a-11e8-8a0c-e73eb689b2fb.gif + +* Quick menu search from the app drawer + + .. image:: https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif + +Features for mobile: + +* App-specific submenus are shown on full screen when toggling them from the + "hamburger" menu + + .. image:: https://user-images.githubusercontent.com/973709/48417297-51286c80-e74a-11e8-9a47-22c810b18c43.gif + +* View type picker dropdown displays confortably + + .. image:: https://user-images.githubusercontent.com/973709/50964322-e3d55580-14c6-11e9-8249-48db9539600f.gif + +* Top app bar is always visible, but the control panel is hidden when + scrolling down, to save some vaulable vertical space + + .. image:: https://user-images.githubusercontent.com/973709/50964496-5cd4ad00-14c7-11e9-9261-fd223a329d02.gif + +* Form status bar action and status buttons are collapsed in dropdowns. + Other control panel buttons use icons to save space. + + .. image:: https://user-images.githubusercontent.com/973709/50965446-e08f9900-14c9-11e9-92d6-dda472cb6557.gif + +* Breadcrumbs navigation is collapsed with a "back arrow" button. + + .. image:: https://user-images.githubusercontent.com/973709/50965168-1d0ec500-14c9-11e9-82a0-dfee82ed0861.gif + +Features for computers: + +* Keyboard shortcuts for easier navigation, **using ``Alt + Shift + [key]``** + combination instead of just ``Alt + [key]``. + See https://github.com/odoo/odoo/issues/30068 to understand why. + + .. image:: https://user-images.githubusercontent.com/973709/48417578-ff341680-e74a-11e8-8881-017709e912bc.png + + +* Autofocus on search menu box when opening the drawer + + .. image:: https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif + +* Set chatter on the side of the screen, optional per user + + .. image:: https://user-images.githubusercontent.com/973709/48417270-41108d00-e74a-11e8-9172-cba825d027ed.gif + +* Full width form sheets + + .. image:: https://user-images.githubusercontent.com/973709/48417428-ac5a5f00-e74a-11e8-8839-5bc538c54c1d.png **Table of contents** @@ -45,36 +93,20 @@ Usage The following keyboard shortcuts are implemented: -* Toggle App Drawer - `ActionKey ` + ``A`` -* Navigate Apps Drawer - Arrow Keys -* Type to select App Links -* ``esc`` to close App Drawer +* Toggle app drawer - ``Alt + Shift + H`` +* Navigate app search results - Arrow keys +* Choose app result - ``Enter`` +* ``Esc`` to close app drawer Known issues / Roadmap ====================== -Note: Data added to the footer ``support_branding`` is not shown while using -this module. - -* Provide full menu search feature instead of just App search -* Drag drawer from left to open in mobile -* Figure out how to test focus on hidden elements for keyboard nav tests -* If you resize the window, body gets a wrong ``overflow: auto`` css property - and you need to refresh your view or open/close the app drawer to fix that. -* Override LESS styling to allow for responsive widget layouts -* Adding ``oe_main_menu_navbar`` ID to the top navigation bar triggers some - great styles, but also `JavaScript that causes issues on mobile - `_ -* Sticky header and footer in list view only works on certain browsers: - https://caniuse.com/#search=sticky (note that the used feature is in - `thead`). -* On Android (FireFox) - clicking the search icon does not - focus the search input. -* On Android (FireFox & Chrome) - clicking the search query input will - show the on screen keyboard for a split second, but the App Drawer - immediately closes and the keyboard closes with it. -* Filter menu items completely on client-side, to make it smoother and allow - users to filter on complete paths of menus and not only on the last item. +* To view the full experience in a device, the page must be loaded with the + device screen size. This means that, if you change the size of your browser, + you should reload the web client to get the full experience for that + new size. This is Odoo's own limitation. +* App navigation with keyboard. +* Make it more beautiful. Maybe OCA-branded? Bug Tracker =========== @@ -82,7 +114,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -118,6 +150,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/web `_ project on GitHub. +This module is part of the `OCA/web `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_responsive/__init__.py b/web_responsive/__init__.py index 359bee1d3..0650744f6 100644 --- a/web_responsive/__init__.py +++ b/web_responsive/__init__.py @@ -1,4 +1 @@ -# Copyright 2018 Alexandre Díaz -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - from . import models diff --git a/web_responsive/__manifest__.py b/web_responsive/__manifest__.py index 88891fcdc..9dc4f11ff 100644 --- a/web_responsive/__manifest__.py +++ b/web_responsive/__manifest__.py @@ -4,11 +4,10 @@ { "name": "Web Responsive", - "summary": "It provides a mobile compliant interface for Odoo Community " - "web", - "version": "11.0.2.0.0", + "summary": "Responsive web client, community-supported", + "version": "12.0.1.0.0", "category": "Website", - "website": "https://laslabs.com/", + "website": "https://github.com/OCA/web", "author": "LasLabs, Tecnativa, Alexandre Díaz, " "Odoo Community Association (OCA)", "license": "LGPL-3", @@ -18,11 +17,11 @@ ], "data": [ 'views/assets.xml', + 'views/res_users.xml', 'views/web.xml', - 'views/inherited_view_users_form_simple_modif.xml', ], 'qweb': [ - 'static/src/xml/app_drawer_menu_search.xml', + 'static/src/xml/apps.xml', 'static/src/xml/form_view.xml', 'static/src/xml/navbar.xml', ], diff --git a/web_responsive/i18n/es.po b/web_responsive/i18n/es.po index 240aa286d..da737bc56 100644 --- a/web_responsive/i18n/es.po +++ b/web_responsive/i18n/es.po @@ -8,53 +8,72 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-12-23 02:13+0000\n" -"PO-Revision-Date: 2018-08-02 06:36+0000\n" -"Last-Translator: Pedro M. Baeza \n" +"POT-Creation-Date: 2019-01-10 10:49+0000\n" +"PO-Revision-Date: 2019-01-10 10:50+0000\n" +"Last-Translator: Jairo Llopis \n" "Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.1.1\n" +"X-Generator: Poedit 2.2\n" #. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.webclient_bootstrap -msgid "Toggle App Drawer" -msgstr "" -"Mostrar/ocultar selector de aplicaciones" - -#. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.webclient_bootstrap -msgid "Toggle Navigation" -msgstr "Mostrar/Ocultar navegación" - -#. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.menu -msgid "Apps" -msgstr "Aplicaciones" - -#. module: web_responsive -#: model:ir.model.fields,field_description:web_responsive.field_res_users_chatter_position +#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position msgid "Chatter Position" msgstr "Posición del chatter" #. module: web_responsive -#: model:ir.model,name:web_responsive.model_ir_http -msgid "HTTP routing" -msgstr "Enrutado HTTP" +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:57 +#: code:addons/web_responsive/static/src/xml/form_view.xml:96 +#, python-format +msgid "Create" +msgstr "Crear" #. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.menu -msgid "More " -msgstr "Más " +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:71 +#: code:addons/web_responsive/static/src/xml/form_view.xml:110 +#, python-format +msgid "Discard" +msgstr "Descartar" + +#. module: web_responsive +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:50 +#, python-format +msgid "Edit" +msgstr "Editar" #. module: web_responsive #: selection:res.users,chatter_position:0 msgid "Normal" msgstr "Normal" +#. module: web_responsive +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:27 +#, python-format +msgid "Quick actions" +msgstr "Acciones rápidas" + +#. module: web_responsive +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:64 +#: code:addons/web_responsive/static/src/xml/form_view.xml:103 +#, python-format +msgid "Save" +msgstr "Guardar" + +#. module: web_responsive +#. openerp-web +#: code:addons/web_responsive/static/src/xml/apps.xml:29 +#, python-format +msgid "Search menus..." +msgstr "Buscar menús..." + #. module: web_responsive #: selection:res.users,chatter_position:0 msgid "Sided" diff --git a/web_responsive/i18n/web_responsive.pot b/web_responsive/i18n/web_responsive.pot index c655e472b..13e8a877f 100644 --- a/web_responsive/i18n/web_responsive.pot +++ b/web_responsive/i18n/web_responsive.pot @@ -4,8 +4,10 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 11.0\n" +"Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-01-10 10:49+0000\n" +"PO-Revision-Date: 2019-01-10 10:49+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -14,52 +16,31 @@ msgstr "" "Plural-Forms: \n" #. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.menu -msgid "Searching:" -msgstr "" - -#. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.menu -msgid "\n" -" &nbsp;|&nbsp;\n" -" " -msgstr "" - -#. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.webclient_bootstrap -msgid "Toggle App Drawer" -msgstr "" - -#. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.webclient_bootstrap -msgid "Toggle Navigation" -msgstr "" - -#. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.menu -msgid "Apps" -msgstr "" - -#. module: web_responsive -#: model:ir.model.fields,field_description:web_responsive.field_res_users_chatter_position +#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position msgid "Chatter Position" msgstr "" #. module: web_responsive #. openerp-web -#: code:addons/web_responsive/static/src/xml/form_view.xml:35 +#: code:addons/web_responsive/static/src/xml/form_view.xml:57 +#: code:addons/web_responsive/static/src/xml/form_view.xml:96 #, python-format -msgid "More" +msgid "Create" msgstr "" #. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.menu -msgid "More " +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:71 +#: code:addons/web_responsive/static/src/xml/form_view.xml:110 +#, python-format +msgid "Discard" msgstr "" #. module: web_responsive -#: model:ir.ui.view,arch_db:web_responsive.menu -msgid "No Search Supplied." +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:50 +#, python-format +msgid "Edit" msgstr "" #. module: web_responsive @@ -68,15 +49,30 @@ msgid "Normal" msgstr "" #. module: web_responsive -#: selection:res.users,chatter_position:0 -msgid "Sided" +#. openerp-web +#: code:addons/web_responsive/static/src/xml/form_view.xml:27 +#, python-format +msgid "Quick actions" msgstr "" #. module: web_responsive #. openerp-web -#: code:addons/web_responsive/static/src/xml/form_view.xml:54 +#: code:addons/web_responsive/static/src/xml/form_view.xml:64 +#: code:addons/web_responsive/static/src/xml/form_view.xml:103 #, python-format -msgid "Task" +msgid "Save" +msgstr "" + +#. module: web_responsive +#. openerp-web +#: code:addons/web_responsive/static/src/xml/apps.xml:29 +#, python-format +msgid "Search menus..." +msgstr "" + +#. module: web_responsive +#: selection:res.users,chatter_position:0 +msgid "Sided" msgstr "" #. module: web_responsive diff --git a/web_responsive/models/__init__.py b/web_responsive/models/__init__.py index e674b2be9..883516533 100644 --- a/web_responsive/models/__init__.py +++ b/web_responsive/models/__init__.py @@ -1,4 +1 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import inherited_res_users +from . import res_users diff --git a/web_responsive/models/inherited_res_users.py b/web_responsive/models/res_users.py similarity index 100% rename from web_responsive/models/inherited_res_users.py rename to web_responsive/models/res_users.py diff --git a/web_responsive/readme/DESCRIPTION.rst b/web_responsive/readme/DESCRIPTION.rst index f3ce74ee6..a8d55a80f 100644 --- a/web_responsive/readme/DESCRIPTION.rst +++ b/web_responsive/readme/DESCRIPTION.rst @@ -1,9 +1,57 @@ -This module provides a mobile compliant interface for Odoo Community web. +This module adds responsiveness to web backend. -Features: +Features for all devices: -* New navigation with an App drawer -* Keyboard shortcuts for easier navigation -* Display kanban views for small screens if an action or field One2x -* Set chatter side (Optional per user) -* Quick search +* New navigation with an app drawer + + .. image:: https://user-images.githubusercontent.com/973709/48417193-09a1e080-e74a-11e8-8a0c-e73eb689b2fb.gif + +* Quick menu search from the app drawer + + .. image:: https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif + +Features for mobile: + +* App-specific submenus are shown on full screen when toggling them from the + "hamburger" menu + + .. image:: https://user-images.githubusercontent.com/973709/48417297-51286c80-e74a-11e8-9a47-22c810b18c43.gif + +* View type picker dropdown displays confortably + + .. image:: https://user-images.githubusercontent.com/973709/50964322-e3d55580-14c6-11e9-8249-48db9539600f.gif + +* Top app bar is always visible, but the control panel is hidden when + scrolling down, to save some vaulable vertical space + + .. image:: https://user-images.githubusercontent.com/973709/50964496-5cd4ad00-14c7-11e9-9261-fd223a329d02.gif + +* Form status bar action and status buttons are collapsed in dropdowns. + Other control panel buttons use icons to save space. + + .. image:: https://user-images.githubusercontent.com/973709/50965446-e08f9900-14c9-11e9-92d6-dda472cb6557.gif + +* Breadcrumbs navigation is collapsed with a "back arrow" button. + + .. image:: https://user-images.githubusercontent.com/973709/50965168-1d0ec500-14c9-11e9-82a0-dfee82ed0861.gif + +Features for computers: + +* Keyboard shortcuts for easier navigation, **using ``Alt + Shift + [key]``** + combination instead of just ``Alt + [key]``. + See https://github.com/odoo/odoo/issues/30068 to understand why. + + .. image:: https://user-images.githubusercontent.com/973709/48417578-ff341680-e74a-11e8-8881-017709e912bc.png + + +* Autofocus on search menu box when opening the drawer + + .. image:: https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif + +* Set chatter on the side of the screen, optional per user + + .. image:: https://user-images.githubusercontent.com/973709/48417270-41108d00-e74a-11e8-9172-cba825d027ed.gif + +* Full width form sheets + + .. image:: https://user-images.githubusercontent.com/973709/48417428-ac5a5f00-e74a-11e8-8839-5bc538c54c1d.png diff --git a/web_responsive/readme/ROADMAP.rst b/web_responsive/readme/ROADMAP.rst index 97afad6dc..d2ca35cd2 100644 --- a/web_responsive/readme/ROADMAP.rst +++ b/web_responsive/readme/ROADMAP.rst @@ -1,22 +1,6 @@ -Note: Data added to the footer ``support_branding`` is not shown while using -this module. - -* Provide full menu search feature instead of just App search -* Drag drawer from left to open in mobile -* Figure out how to test focus on hidden elements for keyboard nav tests -* If you resize the window, body gets a wrong ``overflow: auto`` css property - and you need to refresh your view or open/close the app drawer to fix that. -* Override LESS styling to allow for responsive widget layouts -* Adding ``oe_main_menu_navbar`` ID to the top navigation bar triggers some - great styles, but also `JavaScript that causes issues on mobile - `_ -* Sticky header and footer in list view only works on certain browsers: - https://caniuse.com/#search=sticky (note that the used feature is in - `thead`). -* On Android (FireFox) - clicking the search icon does not - focus the search input. -* On Android (FireFox & Chrome) - clicking the search query input will - show the on screen keyboard for a split second, but the App Drawer - immediately closes and the keyboard closes with it. -* Filter menu items completely on client-side, to make it smoother and allow - users to filter on complete paths of menus and not only on the last item. +* To view the full experience in a device, the page must be loaded with the + device screen size. This means that, if you change the size of your browser, + you should reload the web client to get the full experience for that + new size. This is Odoo's own limitation. +* App navigation with keyboard. +* Make it more beautiful. Maybe OCA-branded? diff --git a/web_responsive/readme/USAGE.rst b/web_responsive/readme/USAGE.rst index 0bf3256ee..a8a1c12bd 100644 --- a/web_responsive/readme/USAGE.rst +++ b/web_responsive/readme/USAGE.rst @@ -1,6 +1,6 @@ The following keyboard shortcuts are implemented: -* Toggle App Drawer - `ActionKey ` + ``A`` -* Navigate Apps Drawer - Arrow Keys -* Type to select App Links -* ``esc`` to close App Drawer +* Toggle app drawer - ``Alt + Shift + H`` +* Navigate app search results - Arrow keys +* Choose app result - ``Enter`` +* ``Esc`` to close app drawer diff --git a/web_responsive/static/description/index.html b/web_responsive/static/description/index.html index 9d34bd445..bb707b6d4 100644 --- a/web_responsive/static/description/index.html +++ b/web_responsive/static/description/index.html @@ -367,15 +367,54 @@ ul.auto-toc { !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: LGPL-3 OCA/web Translate me on Weblate Try me on Runbot

-

This module provides a mobile compliant interface for Odoo Community web.

-

Features:

-
    -
  • New navigation with an App drawer
  • -
  • Keyboard shortcuts for easier navigation
  • -
  • Display kanban views for small screens if an action or field One2x
  • -
  • Set chatter side (Optional per user)
  • -
  • Quick search
  • +

    Beta License: LGPL-3 OCA/web Translate me on Weblate Try me on Runbot

    +

    This module adds responsiveness to web backend.

    +

    Features for all devices:

    +
      +
    • New navigation with an app drawer

      +https://user-images.githubusercontent.com/973709/48417193-09a1e080-e74a-11e8-8a0c-e73eb689b2fb.gif +
    • +
    • Quick menu search from the app drawer

      +https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif +
    • +
    +

    Features for mobile:

    +
      +
    • App-specific submenus are shown on full screen when toggling them from the +“hamburger” menu

      +https://user-images.githubusercontent.com/973709/48417297-51286c80-e74a-11e8-9a47-22c810b18c43.gif +
    • +
    • View type picker dropdown displays confortably

      +https://user-images.githubusercontent.com/973709/50964322-e3d55580-14c6-11e9-8249-48db9539600f.gif +
    • +
    • Top app bar is always visible, but the control panel is hidden when +scrolling down, to save some vaulable vertical space

      +https://user-images.githubusercontent.com/973709/50964496-5cd4ad00-14c7-11e9-9261-fd223a329d02.gif +
    • +
    • Form status bar action and status buttons are collapsed in dropdowns. +Other control panel buttons use icons to save space.

      +https://user-images.githubusercontent.com/973709/50965446-e08f9900-14c9-11e9-92d6-dda472cb6557.gif +
    • +
    • Breadcrumbs navigation is collapsed with a “back arrow” button.

      +https://user-images.githubusercontent.com/973709/50965168-1d0ec500-14c9-11e9-82a0-dfee82ed0861.gif +
    • +
    +

    Features for computers:

    +
      +
    • Keyboard shortcuts for easier navigation, using ``Alt + Shift + [key]`` +combination instead of just Alt + [key]. +See https://github.com/odoo/odoo/issues/30068 to understand why.

      +https://user-images.githubusercontent.com/973709/48417578-ff341680-e74a-11e8-8881-017709e912bc.png +
    • +
    • Autofocus on search menu box when opening the drawer

      +https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif +
    • +
    • Set chatter on the side of the screen, optional per user

      +https://user-images.githubusercontent.com/973709/48417270-41108d00-e74a-11e8-9172-cba825d027ed.gif +
    • +
    • Full width form sheets

      +https://user-images.githubusercontent.com/973709/48417428-ac5a5f00-e74a-11e8-8839-5bc538c54c1d.png +

    Table of contents

    @@ -395,35 +434,21 @@ ul.auto-toc {

    Usage

    The following keyboard shortcuts are implemented:

      -
    • Toggle App Drawer - ActionKey <https://en.wikipedia.org/wiki/Access_key#Access_in_different_browsers> + A
    • -
    • Navigate Apps Drawer - Arrow Keys
    • -
    • Type to select App Links
    • -
    • esc to close App Drawer
    • +
    • Toggle app drawer - Alt + Shift + H
    • +
    • Navigate app search results - Arrow keys
    • +
    • Choose app result - Enter
    • +
    • Esc to close app drawer

    Known issues / Roadmap

    -

    Note: Data added to the footer support_branding is not shown while using -this module.

      -
    • Provide full menu search feature instead of just App search
    • -
    • Drag drawer from left to open in mobile
    • -
    • Figure out how to test focus on hidden elements for keyboard nav tests
    • -
    • If you resize the window, body gets a wrong overflow: auto css property -and you need to refresh your view or open/close the app drawer to fix that.
    • -
    • Override LESS styling to allow for responsive widget layouts
    • -
    • Adding oe_main_menu_navbar ID to the top navigation bar triggers some -great styles, but also JavaScript that causes issues on mobile
    • -
    • Sticky header and footer in list view only works on certain browsers: -https://caniuse.com/#search=sticky (note that the used feature is in -thead).
    • -
    • On Android (FireFox) - clicking the search icon does not -focus the search input.
    • -
    • On Android (FireFox & Chrome) - clicking the search query input will -show the on screen keyboard for a split second, but the App Drawer -immediately closes and the keyboard closes with it.
    • -
    • Filter menu items completely on client-side, to make it smoother and allow -users to filter on complete paths of menus and not only on the last item.
    • +
    • To view the full experience in a device, the page must be loaded with the +device screen size. This means that, if you change the size of your browser, +you should reload the web client to get the full experience for that +new size. This is Odoo’s own limitation.
    • +
    • App navigation with keyboard.
    • +
    • Make it more beautiful. Maybe OCA-branded?
    @@ -431,7 +456,7 @@ users to filter on complete paths of menus and not only on the last item.

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -461,7 +486,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    -

    This module is part of the OCA/web project on GitHub.

    +

    This module is part of the OCA/web project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    diff --git a/web_responsive/static/lib/css/drawer.3.2.2.css b/web_responsive/static/lib/css/drawer.3.2.2.css deleted file mode 100644 index d2c864712..000000000 --- a/web_responsive/static/lib/css/drawer.3.2.2.css +++ /dev/null @@ -1,525 +0,0 @@ -/*! - * jquery-drawer v3.2.2 - * Flexible drawer menu using jQuery, iScroll and CSS. - * http://git.blivesta.com/drawer - * License : MIT - * Author : blivesta (http://blivesta.com/) - */ - -/*!------------------------------------*\ - Base -\*!------------------------------------*/ - -.drawer-open { - overflow: hidden !important; -} - -.drawer-nav { - position: fixed; - z-index: 101; - top: 0; - overflow: hidden; - width: 16.25rem; - height: 100%; - color: #222; - background-color: #fff; -} - -.drawer-brand { - font-size: 1.5rem; - font-weight: bold; - line-height: 3.75rem; - display: block; - padding-right: .75rem; - padding-left: .75rem; - text-decoration: none; - color: #222; -} - -.drawer-menu { - margin: 0; - padding: 0; - list-style: none; -} - -.drawer-menu-item { - font-size: 1rem; - display: block; - padding: .75rem; - text-decoration: none; - color: #222; -} - -.drawer-menu-item:hover { - text-decoration: underline; - color: #555; - background-color: transparent; -} - -/*! overlay */ - -.drawer-overlay { - position: fixed; - z-index: 100; - top: 0; - left: 0; - display: none; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, .2); -} - -.drawer-open .drawer-overlay { - display: block; -} - -/*!------------------------------------*\ - Top -\*!------------------------------------*/ - -.drawer--top .drawer-nav { - top: -100%; - left: 0; - width: 100%; - height: auto; - max-height: 100%; - -webkit-transition: top .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); - transition: top .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); -} - -.drawer--top.drawer-open .drawer-nav { - top: 0; -} - -.drawer--top .drawer-hamburger, -.drawer--top.drawer-open .drawer-hamburger { - right: 0; -} - -/*!------------------------------------*\ - Left -\*!------------------------------------*/ - -.drawer--left .drawer-nav { - left: -16.25rem; - -webkit-transition: left .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); - transition: left .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); -} - -.drawer--left.drawer-open .drawer-nav, -.drawer--left .drawer-hamburger, -.drawer--left.drawer-open .drawer-navbar .drawer-hamburger { - left: 0; -} - -.drawer--left.drawer-open .drawer-hamburger { - left: 16.25rem; -} - -/*!------------------------------------*\ - Right -\*!------------------------------------*/ - -.drawer--right .drawer-nav { - right: -16.25rem; - -webkit-transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); - transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); -} - -.drawer--right.drawer-open .drawer-nav, -.drawer--right .drawer-hamburger, -.drawer--right.drawer-open .drawer-navbar .drawer-hamburger { - right: 0; -} - -.drawer--right.drawer-open .drawer-hamburger { - right: 16.25rem; -} - -/*!------------------------------------*\ - Hamburger -\*!------------------------------------*/ - -.drawer-hamburger { - position: fixed; - z-index: 104; - top: 0; - display: block; - box-sizing: content-box; - width: 2rem; - padding: 0; - padding-top: 18px; - padding-right: .75rem; - padding-bottom: 30px; - padding-left: .75rem; - -webkit-transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); - transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - border: 0; - outline: 0; - background-color: transparent; -} - -.drawer-hamburger:hover { - cursor: pointer; - background-color: transparent; -} - -.drawer-hamburger-icon { - position: relative; - display: block; - margin-top: 10px; -} - -.drawer-hamburger-icon, -.drawer-hamburger-icon:before, -.drawer-hamburger-icon:after { - width: 100%; - height: 2px; - -webkit-transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); - transition: all .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); - background-color: #222; -} - -.drawer-hamburger-icon:before, -.drawer-hamburger-icon:after { - position: absolute; - top: -10px; - left: 0; - content: ' '; -} - -.drawer-hamburger-icon:after { - top: 10px; -} - -.drawer-open .drawer-hamburger-icon { - background-color: transparent; -} - -.drawer-open .drawer-hamburger-icon:before, -.drawer-open .drawer-hamburger-icon:after { - top: 0; -} - -.drawer-open .drawer-hamburger-icon:before { - -webkit-transform: rotate(45deg); - transform: rotate(45deg); -} - -.drawer-open .drawer-hamburger-icon:after { - -webkit-transform: rotate(-45deg); - transform: rotate(-45deg); -} - -/*!------------------------------------*\ - accessibility -\*!------------------------------------*/ - -/*! - * Only display content to screen readers - * See: http://a11yproject.com/posts/how-to-hide-content - */ - -.sr-only { - position: absolute; - overflow: hidden; - clip: rect(0, 0, 0, 0); - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - border: 0; -} - -/*! - * Use in conjunction with .sr-only to only display content when it's focused. - * Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 - * Credit: HTML5 Boilerplate - */ - -.sr-only-focusable:active, -.sr-only-focusable:focus { - position: static; - overflow: visible; - clip: auto; - width: auto; - height: auto; - margin: 0; -} - -/*!------------------------------------*\ - Sidebar -\*!------------------------------------*/ - -.drawer--sidebar { - background-color: #fff; -} - -.drawer--sidebar .drawer-contents { - background-color: #fff; -} - -@media (min-width: 64em) { - .drawer--sidebar .drawer-hamburger { - display: none; - visibility: hidden; - } - - .drawer--sidebar .drawer-nav { - display: block; - -webkit-transform: none; - transform: none; - position: fixed; - width: 12.5rem; - height: 100%; - } - - /*! Left */ - .drawer--sidebar.drawer--left .drawer-nav { - left: 0; - border-right: 1px solid #ddd; - } - - .drawer--sidebar.drawer--left .drawer-contents { - margin-left: 12.5rem; - } - - /*! Right */ - .drawer--sidebar.drawer--right .drawer-nav { - right: 0; - border-left: 1px solid #ddd; - } - - .drawer--sidebar.drawer--right .drawer-contents { - margin-right: 12.5rem; - } - - /*! container */ - .drawer--sidebar .drawer-container { - max-width: 48rem; - } -} - -@media (min-width: 75em) { - .drawer--sidebar .drawer-nav { - width: 16.25rem; - } - - .drawer--sidebar.drawer--left .drawer-contents { - margin-left: 16.25rem; - } - - .drawer--sidebar.drawer--right .drawer-contents { - margin-right: 16.25rem; - } - - /*! container */ - .drawer--sidebar .drawer-container { - max-width: 60rem; - } -} - -/*!------------------------------------*\ - Navbar -\*!------------------------------------*/ - -.drawer--navbarTopGutter { - padding-top: 3.75rem; -} - -.drawer-navbar .drawer-navbar-header { - border-bottom: 1px solid #ddd; - background-color: #fff; -} - -.drawer-navbar { - z-index: 102; - top: 0; - width: 100%; -} - -/*! .drawer-navbar modifier */ - -.drawer-navbar--fixed { - position: fixed; -} - -.drawer-navbar-header { - position: relative; - z-index: 102; - box-sizing: border-box; - width: 100%; - height: 3.75rem; - padding: 0 .75rem; - text-align: center; -} - -.drawer-navbar .drawer-brand { - line-height: 3.75rem; - display: inline-block; - padding-top: 0; - padding-bottom: 0; - text-decoration: none; -} - -.drawer-navbar .drawer-brand:hover { - background-color: transparent; -} - -.drawer-navbar .drawer-nav { - padding-top: 3.75rem; -} - -.drawer-navbar .drawer-menu { - padding-bottom: 7.5rem; -} - -@media (min-width: 64em) { - .drawer-navbar { - height: 3.75rem; - border-bottom: 1px solid #ddd; - background-color: #fff; - } - - .drawer-navbar .drawer-navbar-header { - position: relative; - display: block; - float: left; - width: auto; - padding: 0; - border: 0; - } - - .drawer-navbar .drawer-menu--right { - float: right; - } - - .drawer-navbar .drawer-menu li { - float: left; - } - - .drawer-navbar .drawer-menu-item { - line-height: 3.75rem; - padding-top: 0; - padding-bottom: 0; - } - - .drawer-navbar .drawer-hamburger { - display: none; - } - - .drawer-navbar .drawer-nav { - position: relative; - left: 0; - overflow: visible; - width: auto; - height: 3.75rem; - padding-top: 0; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - - .drawer-navbar .drawer-menu { - padding: 0; - } - - /*! dropdown */ - .drawer-navbar .drawer-dropdown-menu { - position: absolute; - width: 16.25rem; - border: 1px solid #ddd; - } - - .drawer-navbar .drawer-dropdown-menu-item { - padding-left: .75rem; - } -} - -/*!------------------------------------*\ - Dropdown -\*!------------------------------------*/ - -.drawer-dropdown-menu { - display: none; - box-sizing: border-box; - width: 100%; - margin: 0; - padding: 0; - background-color: #fff; -} - -.drawer-dropdown-menu > li { - width: 100%; - list-style: none; -} - -.drawer-dropdown-menu-item { - line-height: 3.75rem; - display: block; - padding: 0; - padding-right: .75rem; - padding-left: 1.5rem; - text-decoration: none; - color: #222; -} - -.drawer-dropdown-menu-item:hover { - text-decoration: underline; - color: #555; - background-color: transparent; -} - -/*! open */ - -.drawer-dropdown.open > .drawer-dropdown-menu { - display: block; -} - -/*! drawer-caret */ - -.drawer-dropdown .drawer-caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 4px; - -webkit-transition: opacity .2s ease, -webkit-transform .2s ease; - transition: opacity .2s ease, -webkit-transform .2s ease; - transition: transform .2s ease, opacity .2s ease; - transition: transform .2s ease, opacity .2s ease, -webkit-transform .2s ease; - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - vertical-align: middle; - border-top: 4px solid; - border-right: 4px solid transparent; - border-left: 4px solid transparent; -} - -/*! open */ - -.drawer-dropdown.open .drawer-caret { - -webkit-transform: rotate(180deg); - transform: rotate(180deg); -} - -/*!------------------------------------*\ - Container -\*!------------------------------------*/ - -.drawer-container { - margin-right: auto; - margin-left: auto; -} - -@media (min-width: 64em) { - .drawer-container { - max-width: 60rem; - } -} - -@media (min-width: 75em) { - .drawer-container { - max-width: 70rem; - } -} diff --git a/web_responsive/static/lib/js/bililiteRange.2.6.js b/web_responsive/static/lib/js/bililiteRange.2.6.js deleted file mode 100644 index 695f58766..000000000 --- a/web_responsive/static/lib/js/bililiteRange.2.6.js +++ /dev/null @@ -1,765 +0,0 @@ -// Cross-broswer implementation of text ranges and selections -// documentation: http://bililite.com/blog/2011/01/17/cross-browser-text-ranges-and-selections/ -// Version: 2.6 -// Copyright (c) 2013 Daniel Wachsstock -// MIT license: -// 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. - -(function(){ - -// a bit of weirdness with IE11: using 'focus' is flaky, even if I'm not bubbling, as far as I can tell. -var focusEvent = 'onfocusin' in document.createElement('input') ? 'focusin' : 'focus'; - -// IE11 normalize is buggy (http://connect.microsoft.com/IE/feedback/details/809424/node-normalize-removes-text-if-dashes-are-present) -var n = document.createElement('div'); -n.appendChild(document.createTextNode('x-')); -n.appendChild(document.createTextNode('x')); -n.normalize(); -var canNormalize = n.firstChild.length == 3; - - -bililiteRange = function(el, debug){ - var ret; - if (debug){ - ret = new NothingRange(); // Easier to force it to use the no-selection type than to try to find an old browser - }else if (window.getSelection && el.setSelectionRange){ - // Standards. Element is an input or textarea - // note that some input elements do not allow selections - try{ - el.selectionStart; // even getting the selection in such an element will throw - ret = new InputRange(); - }catch(e){ - ret = new NothingRange(); - } - }else if (window.getSelection){ - // Standards, with any other kind of element - ret = new W3CRange(); - }else if (document.selection){ - // Internet Explorer - ret = new IERange(); - }else{ - // doesn't support selection - ret = new NothingRange(); - } - ret._el = el; - // determine parent document, as implemented by John McLear - ret._doc = el.ownerDocument; - ret._win = 'defaultView' in ret._doc ? ret._doc.defaultView : ret._doc.parentWindow; - ret._textProp = textProp(el); - ret._bounds = [0, ret.length()]; - // There's no way to detect whether a focus event happened as a result of a click (which should change the selection) - // or as a result of a keyboard event (a tab in) or a script action (el.focus()). So we track it globally, which is a hack, and is likely to fail - // in edge cases (right-clicks, drag-n-drop), and is vulnerable to a lower-down handler preventing bubbling. - // I just don't know a better way. - // I'll hack my event-listening code below, rather than create an entire new bilililiteRange, potentially before the DOM has loaded - if (!('bililiteRangeMouseDown' in ret._doc)){ - var _doc = {_el: ret._doc}; - ret._doc.bililiteRangeMouseDown = false; - bililiteRange.fn.listen.call(_doc, 'mousedown', function() { - ret._doc.bililiteRangeMouseDown = true; - }); - bililiteRange.fn.listen.call(_doc, 'mouseup', function() { - ret._doc.bililiteRangeMouseDown = false; - }); - } - // note that bililiteRangeSelection is an array, which means that copying it only copies the address, which points to the original. - // make sure that we never let it (always do return [bililiteRangeSelection[0], bililiteRangeSelection[1]]), which means never returning - // this._bounds directly - if (!('bililiteRangeSelection' in el)){ - // start tracking the selection - function trackSelection(evt){ - if (evt && evt.which == 9){ - // do tabs my way, by restoring the selection - // there's a flash of the browser's selection, but I don't see a way of avoiding that - ret._nativeSelect(ret._nativeRange(el.bililiteRangeSelection)); - }else{ - el.bililiteRangeSelection = ret._nativeSelection(); - } - } - trackSelection(); - // only IE does this right and allows us to grab the selection before blurring - if ('onbeforedeactivate' in el){ - ret.listen('beforedeactivate', trackSelection); - }else{ - // with standards-based browsers, have to listen for every user interaction - ret.listen('mouseup', trackSelection).listen('keyup', trackSelection); - } - ret.listen(focusEvent, function(){ - // restore the correct selection when the element comes into focus (mouse clicks change the position of the selection) - // Note that Firefox will not fire the focus event until the window/tab is active even if el.focus() is called - // https://bugzilla.mozilla.org/show_bug.cgi?id=566671 - if (!ret._doc.bililiteRangeMouseDown){ - ret._nativeSelect(ret._nativeRange(el.bililiteRangeSelection)); - } - }); - } - if (!('oninput' in el)){ - // give IE8 a chance. Note that this still fails in IE11, which has has oninput on contenteditable elements but does not - // dispatch input events. See http://connect.microsoft.com/IE/feedback/details/794285/ie10-11-input-event-does-not-fire-on-div-with-contenteditable-set - // TODO: revisit this when I have IE11 running on my development machine - var inputhack = function() {ret.dispatch({type: 'input', bubbles: true}) }; - ret.listen('keyup', inputhack); - ret.listen('cut', inputhack); - ret.listen('paste', inputhack); - ret.listen('drop', inputhack); - el.oninput = 'patched'; - } - return ret; -} - -function textProp(el){ - // returns the property that contains the text of the element - // note that for elements the text attribute represents the obsolete text color, not the textContent. - // we document that these routines do not work for elements so that should not be relevant - - // Bugfix for https://github.com/dwachss/bililiteRange/issues/18 - // Adding typeof check of string for el.value in case for li elements - if (typeof el.value === 'string') return 'value'; - if (typeof el.text != 'undefined') return 'text'; - if (typeof el.textContent != 'undefined') return 'textContent'; - return 'innerText'; -} - -// base class -function Range(){} -Range.prototype = { - length: function() { - return this._el[this._textProp].replace(/\r/g, '').length; // need to correct for IE's CrLf weirdness - }, - bounds: function(s){ - if (bililiteRange.bounds[s]){ - this._bounds = bililiteRange.bounds[s].apply(this); - }else if (s){ - this._bounds = s; // don't do error checking now; things may change at a moment's notice - }else{ - var b = [ - Math.max(0, Math.min (this.length(), this._bounds[0])), - Math.max(0, Math.min (this.length(), this._bounds[1])) - ]; - b[1] = Math.max(b[0], b[1]); - return b; // need to constrain it to fit - } - return this; // allow for chaining - }, - select: function(){ - var b = this._el.bililiteRangeSelection = this.bounds(); - if (this._el === this._doc.activeElement){ - // only actually select if this element is active! - this._nativeSelect(this._nativeRange(b)); - } - this.dispatch({type: 'select', bubbles: true}); - return this; // allow for chaining - }, - text: function(text, select){ - if (arguments.length){ - var bounds = this.bounds(), el = this._el; - // signal the input per DOM 3 input events, http://www.w3.org/TR/DOM-Level-3-Events/#h4_events-inputevents - // we add another field, bounds, which are the bounds of the original text before being changed. - this.dispatch({type: 'beforeinput', bubbles: true, - data: text, bounds: bounds}); - this._nativeSetText(text, this._nativeRange(bounds)); - if (select == 'start'){ - this.bounds ([bounds[0], bounds[0]]); - }else if (select == 'end'){ - this.bounds ([bounds[0]+text.length, bounds[0]+text.length]); - }else if (select == 'all'){ - this.bounds ([bounds[0], bounds[0]+text.length]); - } - this.dispatch({type: 'input', bubbles: true, - data: text, bounds: bounds}); - return this; // allow for chaining - }else{ - return this._nativeGetText(this._nativeRange(this.bounds())).replace(/\r/g, ''); // need to correct for IE's CrLf weirdness - } - }, - insertEOL: function (){ - this._nativeEOL(); - this._bounds = [this._bounds[0]+1, this._bounds[0]+1]; // move past the EOL marker - return this; - }, - sendkeys: function (text){ - var self = this; - this.data().sendkeysOriginalText = this.text(); - this.data().sendkeysBounds = undefined; - function simplechar (rng, c){ - if (/^{[^}]*}$/.test(c)) c = c.slice(1,-1); // deal with unknown {key}s - for (var i =0; i < c.length; ++i){ - var x = c.charCodeAt(i); - rng.dispatch({type: 'keypress', bubbles: true, keyCode: x, which: x, charCode: x}); - } - rng.text(c, 'end'); - } - text.replace(/{[^}]*}|[^{]+|{/g, function(part){ - (bililiteRange.sendkeys[part] || simplechar)(self, part, simplechar); - }); - this.bounds(this.data().sendkeysBounds); - this.dispatch({type: 'sendkeys', which: text}); - return this; - }, - top: function(){ - return this._nativeTop(this._nativeRange(this.bounds())); - }, - scrollIntoView: function(scroller){ - var top = this.top(); - // scroll into position if necessary - if (this._el.scrollTop > top || this._el.scrollTop+this._el.clientHeight < top){ - if (scroller){ - scroller.call(this._el, top); - }else{ - this._el.scrollTop = top; - } - } - return this; - }, - wrap: function (n){ - this._nativeWrap(n, this._nativeRange(this.bounds())); - return this; - }, - selection: function(text){ - if (arguments.length){ - return this.bounds('selection').text(text, 'end').select(); - }else{ - return this.bounds('selection').text(); - } - }, - clone: function(){ - return bililiteRange(this._el).bounds(this.bounds()); - }, - all: function(text){ - if (arguments.length){ - this.dispatch ({type: 'beforeinput', bubbles: true, data: text}); - this._el[this._textProp] = text; - this.dispatch ({type: 'input', bubbles: true, data: text}); - return this; - }else{ - return this._el[this._textProp].replace(/\r/g, ''); // need to correct for IE's CrLf weirdness - } - }, - element: function() { return this._el }, - // includes a quickie polyfill for CustomEvent for IE that isn't perfect but works for me - // IE10 allows custom events but not "new CustomEvent"; have to do it the old-fashioned way - dispatch: function(opts){ - opts = opts || {}; - var event = document.createEvent ? document.createEvent('CustomEvent') : this._doc.createEventObject(); - event.initCustomEvent && event.initCustomEvent(opts.type, !!opts.bubbles, !!opts.cancelable, opts.detail); - for (var key in opts) event[key] = opts[key]; - // dispatch event asynchronously (in the sense of on the next turn of the event loop; still should be fired in order of dispatch - var el = this._el; - setTimeout(function(){ - try { - el.dispatchEvent ? el.dispatchEvent(event) : el.fireEvent("on" + opts.type, document.createEventObject()); - }catch(e){ - // IE8 will not let me fire custom events at all. Call them directly - var listeners = el['listen'+opts.type]; - if (listeners) for (var i = 0; i < listeners.length; ++i){ - listeners[i].call(el, event); - } - } - }, 0); - return this; - }, - listen: function (type, func){ - var el = this._el; - if (el.addEventListener){ - el.addEventListener(type, func); - }else{ - el.attachEvent("on" + type, func); - // IE8 can't even handle custom events created with createEventObject (though it permits attachEvent), so we have to make our own - var listeners = el['listen'+type] = el['listen'+type] || []; - listeners.push(func); - } - return this; - }, - dontlisten: function (type, func){ - var el = this._el; - if (el.removeEventListener){ - el.removeEventListener(type, func); - }else try{ - el.detachEvent("on" + type, func); - }catch(e){ - var listeners = el['listen'+type]; - if (listeners) for (var i = 0; i < listeners.length; ++i){ - if (listeners[i] === func) listeners[i] = function(){}; // replace with a noop - } - } - return this; - } -}; - -// allow extensions ala jQuery -bililiteRange.fn = Range.prototype; // to allow monkey patching -bililiteRange.extend = function(fns){ - for (fn in fns) Range.prototype[fn] = fns[fn]; -}; - -//bounds functions -bililiteRange.bounds = { - all: function() { return [0, this.length()] }, - start: function () { return [0,0] }, - end: function () { return [this.length(), this.length()] }, - selection: function(){ - if (this._el === this._doc.activeElement){ - this.bounds ('all'); // first select the whole thing for constraining - return this._nativeSelection(); - }else{ - return this._el.bililiteRangeSelection; - } - } -}; - -// sendkeys functions -bililiteRange.sendkeys = { - '{enter}': function (rng){ - rng.dispatch({type: 'keypress', bubbles: true, keyCode: '\n', which: '\n', charCode: '\n'}); - rng.insertEOL(); - }, - '{tab}': function (rng, c, simplechar){ - simplechar(rng, '\t'); // useful for inserting what would be whitespace - }, - '{newline}': function (rng, c, simplechar){ - simplechar(rng, '\n'); // useful for inserting what would be whitespace (and if I don't want to use insertEOL, which does some fancy things) - }, - '{backspace}': function (rng){ - var b = rng.bounds(); - if (b[0] == b[1]) rng.bounds([b[0]-1, b[0]]); // no characters selected; it's just an insertion point. Remove the previous character - rng.text('', 'end'); // delete the characters and update the selection - }, - '{del}': function (rng){ - var b = rng.bounds(); - if (b[0] == b[1]) rng.bounds([b[0], b[0]+1]); // no characters selected; it's just an insertion point. Remove the next character - rng.text('', 'end'); // delete the characters and update the selection - }, - '{rightarrow}': function (rng){ - var b = rng.bounds(); - if (b[0] == b[1]) ++b[1]; // no characters selected; it's just an insertion point. Move to the right - rng.bounds([b[1], b[1]]); - }, - '{leftarrow}': function (rng){ - var b = rng.bounds(); - if (b[0] == b[1]) --b[0]; // no characters selected; it's just an insertion point. Move to the left - rng.bounds([b[0], b[0]]); - }, - '{selectall}' : function (rng){ - rng.bounds('all'); - }, - '{selection}': function (rng){ - // insert the characters without the sendkeys processing - var s = rng.data().sendkeysOriginalText; - for (var i =0; i < s.length; ++i){ - var x = s.charCodeAt(i); - rng.dispatch({type: 'keypress', bubbles: true, keyCode: x, which: x, charCode: x}); - } - rng.text(s, 'end'); - }, - '{mark}' : function (rng){ - rng.data().sendkeysBounds = rng.bounds(); - } -}; -// Synonyms from the proposed DOM standard (http://www.w3.org/TR/DOM-Level-3-Events-key/) -bililiteRange.sendkeys['{Enter}'] = bililiteRange.sendkeys['{enter}']; -bililiteRange.sendkeys['{Backspace}'] = bililiteRange.sendkeys['{backspace}']; -bililiteRange.sendkeys['{Delete}'] = bililiteRange.sendkeys['{del}']; -bililiteRange.sendkeys['{ArrowRight}'] = bililiteRange.sendkeys['{rightarrow}']; -bililiteRange.sendkeys['{ArrowLeft}'] = bililiteRange.sendkeys['{leftarrow}']; - -function IERange(){} -IERange.prototype = new Range(); -IERange.prototype._nativeRange = function (bounds){ - var rng; - if (this._el.tagName == 'INPUT'){ - // IE 8 is very inconsistent; textareas have createTextRange but it doesn't work - rng = this._el.createTextRange(); - }else{ - rng = this._doc.body.createTextRange (); - rng.moveToElementText(this._el); - } - if (bounds){ - if (bounds[1] < 0) bounds[1] = 0; // IE tends to run elements out of bounds - if (bounds[0] > this.length()) bounds[0] = this.length(); - if (bounds[1] < rng.text.replace(/\r/g, '').length){ // correct for IE's CrLf weirdness - // block-display elements have an invisible, uncounted end of element marker, so we move an extra one and use the current length of the range - rng.moveEnd ('character', -1); - rng.moveEnd ('character', bounds[1]-rng.text.replace(/\r/g, '').length); - } - if (bounds[0] > 0) rng.moveStart('character', bounds[0]); - } - return rng; -}; -IERange.prototype._nativeSelect = function (rng){ - rng.select(); -}; -IERange.prototype._nativeSelection = function (){ - // returns [start, end] for the selection constrained to be in element - var rng = this._nativeRange(); // range of the element to constrain to - var len = this.length(); - var sel = this._doc.selection.createRange(); - try{ - return [ - iestart(sel, rng), - ieend (sel, rng) - ]; - }catch (e){ - // TODO: determine if this is still necessary, since we only call _nativeSelection if _el is active - // IE gets upset sometimes about comparing text to input elements, but the selections cannot overlap, so make a best guess - return (sel.parentElement().sourceIndex < this._el.sourceIndex) ? [0,0] : [len, len]; - } -}; -IERange.prototype._nativeGetText = function (rng){ - return rng.text; -}; -IERange.prototype._nativeSetText = function (text, rng){ - rng.text = text; -}; -IERange.prototype._nativeEOL = function(){ - if ('value' in this._el){ - this.text('\n'); // for input and textarea, insert it straight - }else{ - this._nativeRange(this.bounds()).pasteHTML('\n
    '); - } -}; -IERange.prototype._nativeTop = function(rng){ - var startrng = this._nativeRange([0,0]); - return rng.boundingTop - startrng.boundingTop; -} -IERange.prototype._nativeWrap = function(n, rng) { - // hacky to use string manipulation but I don't see another way to do it. - var div = document.createElement('div'); - div.appendChild(n); - // insert the existing range HTML after the first tag - var html = div.innerHTML.replace('><', '>'+rng.htmlText+'<'); - rng.pasteHTML(html); -}; - -// IE internals -function iestart(rng, constraint){ - // returns the position (in character) of the start of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after - var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf weirdness - if (rng.compareEndPoints ('StartToStart', constraint) <= 0) return 0; // at or before the beginning - if (rng.compareEndPoints ('StartToEnd', constraint) >= 0) return len; - for (var i = 0; rng.compareEndPoints ('StartToStart', constraint) > 0; ++i, rng.moveStart('character', -1)); - return i; -} -function ieend (rng, constraint){ - // returns the position (in character) of the end of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after - var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf weirdness - if (rng.compareEndPoints ('EndToEnd', constraint) >= 0) return len; // at or after the end - if (rng.compareEndPoints ('EndToStart', constraint) <= 0) return 0; - for (var i = 0; rng.compareEndPoints ('EndToStart', constraint) > 0; ++i, rng.moveEnd('character', -1)); - return i; -} - -// an input element in a standards document. "Native Range" is just the bounds array -function InputRange(){} -InputRange.prototype = new Range(); -InputRange.prototype._nativeRange = function(bounds) { - return bounds || [0, this.length()]; -}; -InputRange.prototype._nativeSelect = function (rng){ - this._el.setSelectionRange(rng[0], rng[1]); -}; -InputRange.prototype._nativeSelection = function(){ - return [this._el.selectionStart, this._el.selectionEnd]; -}; -InputRange.prototype._nativeGetText = function(rng){ - return this._el.value.substring(rng[0], rng[1]); -}; -InputRange.prototype._nativeSetText = function(text, rng){ - var val = this._el.value; - this._el.value = val.substring(0, rng[0]) + text + val.substring(rng[1]); -}; -InputRange.prototype._nativeEOL = function(){ - this.text('\n'); -}; -InputRange.prototype._nativeTop = function(rng){ - // I can't remember where I found this clever hack to find the location of text in a text area - var clone = this._el.cloneNode(true); - clone.style.visibility = 'hidden'; - clone.style.position = 'absolute'; - this._el.parentNode.insertBefore(clone, this._el); - clone.style.height = '1px'; - clone.value = this._el.value.slice(0, rng[0]); - var top = clone.scrollHeight; - // this gives the bottom of the text, so we have to subtract the height of a single line - clone.value = 'X'; - top -= clone.scrollHeight; - clone.parentNode.removeChild(clone); - return top; -} -InputRange.prototype._nativeWrap = function() {throw new Error("Cannot wrap in a text element")}; - -function W3CRange(){} -W3CRange.prototype = new Range(); -W3CRange.prototype._nativeRange = function (bounds){ - var rng = this._doc.createRange(); - rng.selectNodeContents(this._el); - if (bounds){ - w3cmoveBoundary (rng, bounds[0], true, this._el); - rng.collapse (true); - w3cmoveBoundary (rng, bounds[1]-bounds[0], false, this._el); - } - return rng; -}; -W3CRange.prototype._nativeSelect = function (rng){ - this._win.getSelection().removeAllRanges(); - this._win.getSelection().addRange (rng); -}; -W3CRange.prototype._nativeSelection = function (){ - // returns [start, end] for the selection constrained to be in element - var rng = this._nativeRange(); // range of the element to constrain to - if (this._win.getSelection().rangeCount == 0) return [this.length(), this.length()]; // append to the end - var sel = this._win.getSelection().getRangeAt(0); - return [ - w3cstart(sel, rng), - w3cend (sel, rng) - ]; - } -W3CRange.prototype._nativeGetText = function (rng){ - return String.prototype.slice.apply(this._el.textContent, this.bounds()); - // return rng.toString(); // this fails in IE11 since it insists on inserting \r's before \n's in Ranges. node.textContent works as expected -}; -W3CRange.prototype._nativeSetText = function (text, rng){ - rng.deleteContents(); - rng.insertNode (this._doc.createTextNode(text)); - if (canNormalize) this._el.normalize(); // merge the text with the surrounding text -}; -W3CRange.prototype._nativeEOL = function(){ - var rng = this._nativeRange(this.bounds()); - rng.deleteContents(); - var br = this._doc.createElement('br'); - br.setAttribute ('_moz_dirty', ''); // for Firefox - rng.insertNode (br); - rng.insertNode (this._doc.createTextNode('\n')); - rng.collapse (false); -}; -W3CRange.prototype._nativeTop = function(rng){ - if (this.length == 0) return 0; // no text, no scrolling - if (rng.toString() == ''){ - var textnode = this._doc.createTextNode('X'); - rng.insertNode (textnode); - } - var startrng = this._nativeRange([0,1]); - var top = rng.getBoundingClientRect().top - startrng.getBoundingClientRect().top; - if (textnode) textnode.parentNode.removeChild(textnode); - return top; -} -W3CRange.prototype._nativeWrap = function(n, rng) { - rng.surroundContents(n); -}; - -// W3C internals -function nextnode (node, root){ - // in-order traversal - // we've already visited node, so get kids then siblings - if (node.firstChild) return node.firstChild; - if (node.nextSibling) return node.nextSibling; - if (node===root) return null; - while (node.parentNode){ - // get uncles - node = node.parentNode; - if (node == root) return null; - if (node.nextSibling) return node.nextSibling; - } - return null; -} -function w3cmoveBoundary (rng, n, bStart, el){ - // move the boundary (bStart == true ? start : end) n characters forward, up to the end of element el. Forward only! - // if the start is moved after the end, then an exception is raised - if (n <= 0) return; - var node = rng[bStart ? 'startContainer' : 'endContainer']; - if (node.nodeType == 3){ - // we may be starting somewhere into the text - n += rng[bStart ? 'startOffset' : 'endOffset']; - } - while (node){ - if (node.nodeType == 3){ - var length = node.nodeValue.length; - if (n <= length){ - rng[bStart ? 'setStart' : 'setEnd'](node, n); - // special case: if we end next to a
    , include that node. - if (n == length){ - // skip past zero-length text nodes - for (var next = nextnode (node, el); next && next.nodeType==3 && next.nodeValue.length == 0; next = nextnode(next, el)){ - rng[bStart ? 'setStartAfter' : 'setEndAfter'](next); - } - if (next && next.nodeType == 1 && next.nodeName == "BR") rng[bStart ? 'setStartAfter' : 'setEndAfter'](next); - } - return; - }else{ - rng[bStart ? 'setStartAfter' : 'setEndAfter'](node); // skip past this one - n -= length; // and eat these characters - } - } - node = nextnode (node, el); - } -} -var START_TO_START = 0; // from the w3c definitions -var START_TO_END = 1; -var END_TO_END = 2; -var END_TO_START = 3; -// from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange) -// -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange. - // * Range.END_TO_END compares the end boundary-point of sourceRange to the end boundary-point of range. - // * Range.END_TO_START compares the end boundary-point of sourceRange to the start boundary-point of range. - // * Range.START_TO_END compares the start boundary-point of sourceRange to the end boundary-point of range. - // * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range. -function w3cstart(rng, constraint){ - if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0; // at or before the beginning - if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return constraint.toString().length; - rng = rng.cloneRange(); // don't change the original - rng.setEnd (constraint.endContainer, constraint.endOffset); // they now end at the same place - return constraint.toString().replace(/\r/g, '').length - rng.toString().replace(/\r/g, '').length; -} -function w3cend (rng, constraint){ - if (rng.compareBoundaryPoints (END_TO_END, constraint) >= 0) return constraint.toString().length; // at or after the end - if (rng.compareBoundaryPoints (START_TO_END, constraint) <= 0) return 0; - rng = rng.cloneRange(); // don't change the original - rng.setStart (constraint.startContainer, constraint.startOffset); // they now start at the same place - return rng.toString().replace(/\r/g, '').length; -} - -function NothingRange(){} -NothingRange.prototype = new Range(); -NothingRange.prototype._nativeRange = function(bounds) { - return bounds || [0,this.length()]; -}; -NothingRange.prototype._nativeSelect = function (rng){ // do nothing -}; -NothingRange.prototype._nativeSelection = function(){ - return [0,0]; -}; -NothingRange.prototype._nativeGetText = function (rng){ - return this._el[this._textProp].substring(rng[0], rng[1]); -}; -NothingRange.prototype._nativeSetText = function (text, rng){ - var val = this._el[this._textProp]; - this._el[this._textProp] = val.substring(0, rng[0]) + text + val.substring(rng[1]); -}; -NothingRange.prototype._nativeEOL = function(){ - this.text('\n'); -}; -NothingRange.prototype._nativeTop = function(){ - return 0; -}; -NothingRange.prototype._nativeWrap = function() {throw new Error("Wrapping not implemented")}; - - -// data for elements, similar to jQuery data, but allows for monitoring with custom events -var data = []; // to avoid attaching javascript objects to DOM elements, to avoid memory leaks -bililiteRange.fn.data = function(){ - var index = this.element().bililiteRangeData; - if (index == undefined){ - index = this.element().bililiteRangeData = data.length; - data[index] = new Data(this); - } - return data[index]; -} -try { - Object.defineProperty({},'foo',{}); // IE8 will throw an error - var Data = function(rng) { - // we use JSON.stringify to display the data values. To make some of those non-enumerable, we have to use properties - Object.defineProperty(this, 'values', { - value: {} - }); - Object.defineProperty(this, 'sourceRange', { - value: rng - }); - Object.defineProperty(this, 'toJSON', { - value: function(){ - var ret = {}; - for (var i in Data.prototype) if (i in this.values) ret[i] = this.values[i]; - return ret; - } - }); - // to display all the properties (not just those changed), use JSON.stringify(state.all) - Object.defineProperty(this, 'all', { - get: function(){ - var ret = {}; - for (var i in Data.prototype) ret[i] = this[i]; - return ret; - } - }); - } - - Data.prototype = {}; - Object.defineProperty(Data.prototype, 'values', { - value: {} - }); - Object.defineProperty(Data.prototype, 'monitored', { - value: {} - }); - - bililiteRange.data = function (name, newdesc){ - newdesc = newdesc || {}; - var desc = Object.getOwnPropertyDescriptor(Data.prototype, name) || {}; - if ('enumerable' in newdesc) desc.enumerable = !!newdesc.enumerable; - if (!('enumerable' in desc)) desc.enumerable = true; // default - if ('value' in newdesc) Data.prototype.values[name] = newdesc.value; - if ('monitored' in newdesc) Data.prototype.monitored[name] = newdesc.monitored; - desc.configurable = true; - desc.get = function (){ - if (name in this.values) return this.values[name]; - return Data.prototype.values[name]; - }; - desc.set = function (value){ - this.values[name] = value; - if (Data.prototype.monitored[name]) this.sourceRange.dispatch({ - type: 'bililiteRangeData', - bubbles: true, - detail: {name: name, value: value} - }); - } - Object.defineProperty(Data.prototype, name, desc); - } -}catch(err){ - // if we can't set object property properties, just use old-fashioned properties - Data = function(rng){ this.sourceRange = rng }; - Data.prototype = {}; - bililiteRange.data = function(name, newdesc){ - if ('value' in newdesc) Data.prototype[name] = newdesc.value; - } -} - -})(); - -// Polyfill for forEach, per Mozilla documentation. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill -if (!Array.prototype.forEach) -{ - Array.prototype.forEach = function(fun /*, thisArg */) - { - "use strict"; - - if (this === void 0 || this === null) - throw new TypeError(); - - var t = Object(this); - var len = t.length >>> 0; - if (typeof fun !== "function") - throw new TypeError(); - - var thisArg = arguments.length >= 2 ? arguments[1] : void 0; - for (var i = 0; i < len; i++) - { - if (i in t) - fun.call(thisArg, t[i], i, t); - } - }; -} diff --git a/web_responsive/static/lib/js/drawer.3.2.2.js b/web_responsive/static/lib/js/drawer.3.2.2.js deleted file mode 100644 index 98e669f7f..000000000 --- a/web_responsive/static/lib/js/drawer.3.2.2.js +++ /dev/null @@ -1,183 +0,0 @@ -/*! - * jquery-drawer v3.2.2 - * Flexible drawer menu using jQuery, iScroll and CSS. - * http://git.blivesta.com/drawer - * License : MIT - * Author : blivesta (http://blivesta.com/) - */ - -;(function umd(factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - define(['jquery'], factory); - } else if (typeof exports === 'object') { - module.exports = factory(require('jquery')); - } else { - factory(jQuery); - } -}(function Drawer($) { - 'use strict'; - var namespace = 'drawer'; - var touches = typeof document.ontouchstart != 'undefined'; - var __ = { - init: function init(options) { - options = $.extend({ - iscroll: { - mouseWheel: true, - preventDefault: false - }, - showOverlay: true - }, options); - - __.settings = { - state: false, - events: { - opened: 'drawer.opened', - closed: 'drawer.closed' - }, - dropdownEvents: { - opened: 'shown.bs.dropdown', - closed: 'hidden.bs.dropdown' - } - }; - - __.settings.class = $.extend({ - nav: 'drawer-nav', - toggle: 'drawer-toggle', - overlay: 'drawer-overlay', - open: 'drawer-open', - close: 'drawer-close', - dropdown: 'drawer-dropdown' - }, options.class); - - return this.each(function instantiateDrawer() { - var _this = this; - var $this = $(this); - var data = $this.data(namespace); - - if (!data) { - options = $.extend({}, options); - $this.data(namespace, { options: options }); - - __.refresh.call(_this); - - if (options.showOverlay) { - __.addOverlay.call(_this); - } - - $('.' + __.settings.class.toggle).on('click.' + namespace, function toggle() { - __.toggle.call(_this); - return _this.iScroll.refresh(); - }); - - $(window).resize(function close() { - __.close.call(_this); - return _this.iScroll.refresh(); - }); - - $('.' + __.settings.class.dropdown) - .on(__.settings.dropdownEvents.opened + ' ' + __.settings.dropdownEvents.closed, function onOpenedOrClosed() { - return _this.iScroll.refresh(); - }); - } - - }); // end each - }, - - refresh: function refresh() { - this.iScroll = new IScroll( - '.' + __.settings.class.nav, - $(this).data(namespace).options.iscroll - ); - }, - - addOverlay: function addOverlay() { - var _this = this; - var $this = $(this); - var $overlay = $('
    ').addClass(__.settings.class.overlay + ' ' + __.settings.class.toggle); - - return $this.append($overlay); - }, - - toggle: function toggle() { - var _this = this; - - if (__.settings.state) { - return __.close.call(_this); - } else { - return __.open.call(_this); - } - }, - - open: function open() { - var $this = $(this); - - if (touches) { - $this.on('touchmove.' + namespace, function disableTouch(event) { - event.preventDefault(); - }); - } - - return $this - .removeClass(__.settings.class.close) - .addClass(__.settings.class.open) - // XXX: local patch waiting for: - // https://github.com/blivesta/drawer/pull/36 - //.css({ 'overflow': 'hidden' }) - // end local patch - .drawerCallback(function triggerOpenedListeners() { - __.settings.state = true; - $this.trigger(__.settings.events.opened); - }); - }, - - close: function close() { - var $this = $(this); - - if (touches) $this.off('touchmove.' + namespace); - - return $this - .removeClass(__.settings.class.open) - .addClass(__.settings.class.close) - // XXX: local patch waiting for: - // https://github.com/blivesta/drawer/pull/36 - //.css("overflow", "auto") - // end local patch - .drawerCallback(function triggerClosedListeners() { - __.settings.state = false; - $this.trigger(__.settings.events.closed); - }); - }, - - destroy: function destroy() { - return this.each(function destroyEach() { - var $this = $(this); - $(window).off('.' + namespace); - $this.removeData(namespace); - }); - } - - }; - - $.fn.drawerCallback = function drawerCallback(callback) { - var end = 'transitionend webkitTransitionEnd'; - return this.each(function setAnimationEndHandler() { - var $this = $(this); - $this.on(end, function invokeCallbackOnAnimationEnd() { - $this.off(end); - return callback.call(this); - }); - }); - }; - - $.fn.drawer = function drawer(method) { - if (__[method]) { - return __[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method) { - return __.init.apply(this, arguments); - } else { - $.error('Method ' + method + ' does not exist on jQuery.' + namespace); - } - }; - -})); diff --git a/web_responsive/static/lib/js/iscroll-probe.5.2.0.js b/web_responsive/static/lib/js/iscroll-probe.5.2.0.js deleted file mode 100644 index 8283a36f0..000000000 --- a/web_responsive/static/lib/js/iscroll-probe.5.2.0.js +++ /dev/null @@ -1,2197 +0,0 @@ -/*! iScroll v5.2.0-snapshot ~ (c) 2008-2017 Matteo Spinelli ~ http://cubiq.org/license */ -(function (window, document, Math) { -var rAF = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function (callback) { window.setTimeout(callback, 1000 / 60); }; - -var utils = (function () { - var me = {}; - - var _elementStyle = document.createElement('div').style; - var _vendor = (function () { - var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'], - transform, - i = 0, - l = vendors.length; - - for ( ; i < l; i++ ) { - transform = vendors[i] + 'ransform'; - if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1); - } - - return false; - })(); - - function _prefixStyle (style) { - if ( _vendor === false ) return false; - if ( _vendor === '' ) return style; - return _vendor + style.charAt(0).toUpperCase() + style.substr(1); - } - - me.getTime = Date.now || function getTime () { return new Date().getTime(); }; - - me.extend = function (target, obj) { - for ( var i in obj ) { - target[i] = obj[i]; - } - }; - - me.addEvent = function (el, type, fn, capture) { - el.addEventListener(type, fn, !!capture); - }; - - me.removeEvent = function (el, type, fn, capture) { - el.removeEventListener(type, fn, !!capture); - }; - - me.prefixPointerEvent = function (pointerEvent) { - return window.MSPointerEvent ? - 'MSPointer' + pointerEvent.charAt(7).toUpperCase() + pointerEvent.substr(8): - pointerEvent; - }; - - me.momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) { - var distance = current - start, - speed = Math.abs(distance) / time, - destination, - duration; - - deceleration = deceleration === undefined ? 0.0006 : deceleration; - - destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); - duration = speed / deceleration; - - if ( destination < lowerMargin ) { - destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin; - distance = Math.abs(destination - current); - duration = distance / speed; - } else if ( destination > 0 ) { - destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0; - distance = Math.abs(current) + destination; - duration = distance / speed; - } - - return { - destination: Math.round(destination), - duration: duration - }; - }; - - var _transform = _prefixStyle('transform'); - - me.extend(me, { - hasTransform: _transform !== false, - hasPerspective: _prefixStyle('perspective') in _elementStyle, - hasTouch: 'ontouchstart' in window, - hasPointer: !!(window.PointerEvent || window.MSPointerEvent), // IE10 is prefixed - hasTransition: _prefixStyle('transition') in _elementStyle - }); - - /* - This should find all Android browsers lower than build 535.19 (both stock browser and webview) - - galaxy S2 is ok - - 2.3.6 : `AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1` - - 4.0.4 : `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` - - galaxy S3 is badAndroid (stock brower, webview) - `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` - - galaxy S4 is badAndroid (stock brower, webview) - `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` - - galaxy S5 is OK - `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)` - - galaxy S6 is OK - `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)` - */ - me.isBadAndroid = (function() { - var appVersion = window.navigator.appVersion; - // Android browser is not a chrome browser. - if (/Android/.test(appVersion) && !(/Chrome\/\d/.test(appVersion))) { - var safariVersion = appVersion.match(/Safari\/(\d+.\d)/); - if(safariVersion && typeof safariVersion === "object" && safariVersion.length >= 2) { - return parseFloat(safariVersion[1]) < 535.19; - } else { - return true; - } - } else { - return false; - } - })(); - - me.extend(me.style = {}, { - transform: _transform, - transitionTimingFunction: _prefixStyle('transitionTimingFunction'), - transitionDuration: _prefixStyle('transitionDuration'), - transitionDelay: _prefixStyle('transitionDelay'), - transformOrigin: _prefixStyle('transformOrigin'), - touchAction: _prefixStyle('touchAction') - }); - - me.hasClass = function (e, c) { - var re = new RegExp("(^|\\s)" + c + "(\\s|$)"); - return re.test(e.className); - }; - - me.addClass = function (e, c) { - if ( me.hasClass(e, c) ) { - return; - } - - var newclass = e.className.split(' '); - newclass.push(c); - e.className = newclass.join(' '); - }; - - me.removeClass = function (e, c) { - if ( !me.hasClass(e, c) ) { - return; - } - - var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g'); - e.className = e.className.replace(re, ' '); - }; - - me.offset = function (el) { - var left = -el.offsetLeft, - top = -el.offsetTop; - - // jshint -W084 - while (el = el.offsetParent) { - left -= el.offsetLeft; - top -= el.offsetTop; - } - // jshint +W084 - - return { - left: left, - top: top - }; - }; - - me.preventDefaultException = function (el, exceptions) { - for ( var i in exceptions ) { - if ( exceptions[i].test(el[i]) ) { - return true; - } - } - - return false; - }; - - me.extend(me.eventType = {}, { - touchstart: 1, - touchmove: 1, - touchend: 1, - - mousedown: 2, - mousemove: 2, - mouseup: 2, - - pointerdown: 3, - pointermove: 3, - pointerup: 3, - - MSPointerDown: 3, - MSPointerMove: 3, - MSPointerUp: 3 - }); - - me.extend(me.ease = {}, { - quadratic: { - style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', - fn: function (k) { - return k * ( 2 - k ); - } - }, - circular: { - style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1) - fn: function (k) { - return Math.sqrt( 1 - ( --k * k ) ); - } - }, - back: { - style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', - fn: function (k) { - var b = 4; - return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1; - } - }, - bounce: { - style: '', - fn: function (k) { - if ( ( k /= 1 ) < ( 1 / 2.75 ) ) { - return 7.5625 * k * k; - } else if ( k < ( 2 / 2.75 ) ) { - return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; - } else if ( k < ( 2.5 / 2.75 ) ) { - return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; - } else { - return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; - } - } - }, - elastic: { - style: '', - fn: function (k) { - var f = 0.22, - e = 0.4; - - if ( k === 0 ) { return 0; } - if ( k == 1 ) { return 1; } - - return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 ); - } - } - }); - - me.tap = function (e, eventName) { - var ev = document.createEvent('Event'); - ev.initEvent(eventName, true, true); - ev.pageX = e.pageX; - ev.pageY = e.pageY; - e.target.dispatchEvent(ev); - }; - - me.click = function (e) { - var target = e.target, - ev; - - if ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) { - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent - // initMouseEvent is deprecated. - ev = document.createEvent(window.MouseEvent ? 'MouseEvents' : 'Event'); - ev.initEvent('click', true, true); - ev.view = e.view || window; - ev.detail = 1; - ev.screenX = target.screenX || 0; - ev.screenY = target.screenY || 0; - ev.clientX = target.clientX || 0; - ev.clientY = target.clientY || 0; - ev.ctrlKey = !!e.ctrlKey; - ev.altKey = !!e.altKey; - ev.shiftKey = !!e.shiftKey; - ev.metaKey = !!e.metaKey; - ev.button = 0; - ev.relatedTarget = null; - ev._constructed = true; - target.dispatchEvent(ev); - } - }; - - me.getTouchAction = function(eventPassthrough, addPinch) { - var touchAction = 'none'; - if ( eventPassthrough === 'vertical' ) { - touchAction = 'pan-y'; - } else if (eventPassthrough === 'horizontal' ) { - touchAction = 'pan-x'; - } - if (addPinch && touchAction != 'none') { - // add pinch-zoom support if the browser supports it, but if not (eg. Chrome <55) do nothing - touchAction += ' pinch-zoom'; - } - return touchAction; - }; - - me.getRect = function(el) { - if (el instanceof SVGElement) { - var rect = el.getBoundingClientRect(); - return { - top : rect.top, - left : rect.left, - width : rect.width, - height : rect.height - }; - } else { - return { - top : el.offsetTop, - left : el.offsetLeft, - width : el.offsetWidth, - height : el.offsetHeight - }; - } - }; - - return me; -})(); -function IScroll (el, options) { - this.wrapper = typeof el == 'string' ? document.querySelector(el) : el; - this.scroller = this.wrapper.children[0]; - this.scrollerStyle = this.scroller.style; // cache style for better performance - - this.options = { - - resizeScrollbars: true, - - mouseWheelSpeed: 20, - - snapThreshold: 0.334, - -// INSERT POINT: OPTIONS - disablePointer : !utils.hasPointer, - disableTouch : utils.hasPointer || !utils.hasTouch, - disableMouse : utils.hasPointer || utils.hasTouch, - startX: 0, - startY: 0, - scrollY: true, - directionLockThreshold: 5, - momentum: true, - - bounce: true, - bounceTime: 600, - bounceEasing: '', - - preventDefault: true, - preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }, - - HWCompositing: true, - useTransition: true, - useTransform: true, - bindToWrapper: typeof window.onmousedown === "undefined" - }; - - for ( var i in options ) { - this.options[i] = options[i]; - } - - // Normalize options - this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : ''; - - this.options.useTransition = utils.hasTransition && this.options.useTransition; - this.options.useTransform = utils.hasTransform && this.options.useTransform; - - this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough; - this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault; - - // If you want eventPassthrough I have to lock one of the axes - this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY; - this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX; - - // With eventPassthrough we also need lockDirection mechanism - this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough; - this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold; - - this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing; - - this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling; - - if ( this.options.tap === true ) { - this.options.tap = 'tap'; - } - - // https://github.com/cubiq/iscroll/issues/1029 - if (!this.options.useTransition && !this.options.useTransform) { - if(!(/relative|absolute/i).test(this.scrollerStyle.position)) { - this.scrollerStyle.position = "relative"; - } - } - - if ( this.options.shrinkScrollbars == 'scale' ) { - this.options.useTransition = false; - } - - this.options.invertWheelDirection = this.options.invertWheelDirection ? -1 : 1; - - if ( this.options.probeType == 3 ) { - this.options.useTransition = false; } - -// INSERT POINT: NORMALIZATION - - // Some defaults - this.x = 0; - this.y = 0; - this.directionX = 0; - this.directionY = 0; - this._events = {}; - -// INSERT POINT: DEFAULTS - - this._init(); - this.refresh(); - - this.scrollTo(this.options.startX, this.options.startY); - this.enable(); -} - -IScroll.prototype = { - version: '5.2.0-snapshot', - - _init: function () { - this._initEvents(); - - if ( this.options.scrollbars || this.options.indicators ) { - this._initIndicators(); - } - - if ( this.options.mouseWheel ) { - this._initWheel(); - } - - if ( this.options.snap ) { - this._initSnap(); - } - - if ( this.options.keyBindings ) { - this._initKeys(); - } - -// INSERT POINT: _init - - }, - - destroy: function () { - this._initEvents(true); - clearTimeout(this.resizeTimeout); - this.resizeTimeout = null; - this._execEvent('destroy'); - }, - - _transitionEnd: function (e) { - if ( e.target != this.scroller || !this.isInTransition ) { - return; - } - - this._transitionTime(); - if ( !this.resetPosition(this.options.bounceTime) ) { - this.isInTransition = false; - this._execEvent('scrollEnd'); - } - }, - - _start: function (e) { - // React to left mouse button only - if ( utils.eventType[e.type] != 1 ) { - // for button property - // http://unixpapa.com/js/mouse.html - var button; - if (!e.which) { - /* IE case */ - button = (e.button < 2) ? 0 : - ((e.button == 4) ? 1 : 2); - } else { - /* All others */ - button = e.button; - } - if ( button !== 0 ) { - return; - } - } - - if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) { - return; - } - - if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { - e.preventDefault(); - } - - var point = e.touches ? e.touches[0] : e, - pos; - - this.initiated = utils.eventType[e.type]; - this.moved = false; - this.distX = 0; - this.distY = 0; - this.directionX = 0; - this.directionY = 0; - this.directionLocked = 0; - - this.startTime = utils.getTime(); - - if ( this.options.useTransition && this.isInTransition ) { - this._transitionTime(); - this.isInTransition = false; - pos = this.getComputedPosition(); - this._translate(Math.round(pos.x), Math.round(pos.y)); - this._execEvent('scrollEnd'); - } else if ( !this.options.useTransition && this.isAnimating ) { - this.isAnimating = false; - this._execEvent('scrollEnd'); - } - - this.startX = this.x; - this.startY = this.y; - this.absStartX = this.x; - this.absStartY = this.y; - this.pointX = point.pageX; - this.pointY = point.pageY; - - this._execEvent('beforeScrollStart'); - }, - - _move: function (e) { - if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { - return; - } - - if ( this.options.preventDefault ) { // increases performance on Android? TODO: check! - e.preventDefault(); - } - - var point = e.touches ? e.touches[0] : e, - deltaX = point.pageX - this.pointX, - deltaY = point.pageY - this.pointY, - timestamp = utils.getTime(), - newX, newY, - absDistX, absDistY; - - this.pointX = point.pageX; - this.pointY = point.pageY; - - this.distX += deltaX; - this.distY += deltaY; - absDistX = Math.abs(this.distX); - absDistY = Math.abs(this.distY); - - // We need to move at least 10 pixels for the scrolling to initiate - if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) { - return; - } - - // If you are scrolling in one direction lock the other - if ( !this.directionLocked && !this.options.freeScroll ) { - if ( absDistX > absDistY + this.options.directionLockThreshold ) { - this.directionLocked = 'h'; // lock horizontally - } else if ( absDistY >= absDistX + this.options.directionLockThreshold ) { - this.directionLocked = 'v'; // lock vertically - } else { - this.directionLocked = 'n'; // no lock - } - } - - if ( this.directionLocked == 'h' ) { - if ( this.options.eventPassthrough == 'vertical' ) { - e.preventDefault(); - } else if ( this.options.eventPassthrough == 'horizontal' ) { - this.initiated = false; - return; - } - - deltaY = 0; - } else if ( this.directionLocked == 'v' ) { - if ( this.options.eventPassthrough == 'horizontal' ) { - e.preventDefault(); - } else if ( this.options.eventPassthrough == 'vertical' ) { - this.initiated = false; - return; - } - - deltaX = 0; - } - - deltaX = this.hasHorizontalScroll ? deltaX : 0; - deltaY = this.hasVerticalScroll ? deltaY : 0; - - newX = this.x + deltaX; - newY = this.y + deltaY; - - // Slow down if outside of the boundaries - if ( newX > 0 || newX < this.maxScrollX ) { - newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX; - } - if ( newY > 0 || newY < this.maxScrollY ) { - newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY; - } - - this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; - this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; - - if ( !this.moved ) { - this._execEvent('scrollStart'); - } - - this.moved = true; - - this._translate(newX, newY); - -/* REPLACE START: _move */ - if ( timestamp - this.startTime > 300 ) { - this.startTime = timestamp; - this.startX = this.x; - this.startY = this.y; - - if ( this.options.probeType == 1 ) { - this._execEvent('scroll'); - } - } - - if ( this.options.probeType > 1 ) { - this._execEvent('scroll'); - } -/* REPLACE END: _move */ - - }, - - _end: function (e) { - if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { - return; - } - - if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { - e.preventDefault(); - } - - var point = e.changedTouches ? e.changedTouches[0] : e, - momentumX, - momentumY, - duration = utils.getTime() - this.startTime, - newX = Math.round(this.x), - newY = Math.round(this.y), - distanceX = Math.abs(newX - this.startX), - distanceY = Math.abs(newY - this.startY), - time = 0, - easing = ''; - - this.isInTransition = 0; - this.initiated = 0; - this.endTime = utils.getTime(); - - // reset if we are outside of the boundaries - if ( this.resetPosition(this.options.bounceTime) ) { - return; - } - - this.scrollTo(newX, newY); // ensures that the last position is rounded - - // we scrolled less than 10 pixels - if ( !this.moved ) { - if ( this.options.tap ) { - utils.tap(e, this.options.tap); - } - - if ( this.options.click ) { - utils.click(e); - } - - this._execEvent('scrollCancel'); - return; - } - - if ( this._events.flick && duration < 200 && distanceX < 100 && distanceY < 100 ) { - this._execEvent('flick'); - return; - } - - // start momentum animation if needed - if ( this.options.momentum && duration < 300 ) { - momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : { destination: newX, duration: 0 }; - momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : { destination: newY, duration: 0 }; - newX = momentumX.destination; - newY = momentumY.destination; - time = Math.max(momentumX.duration, momentumY.duration); - this.isInTransition = 1; - } - - - if ( this.options.snap ) { - var snap = this._nearestSnap(newX, newY); - this.currentPage = snap; - time = this.options.snapSpeed || Math.max( - Math.max( - Math.min(Math.abs(newX - snap.x), 1000), - Math.min(Math.abs(newY - snap.y), 1000) - ), 300); - newX = snap.x; - newY = snap.y; - - this.directionX = 0; - this.directionY = 0; - easing = this.options.bounceEasing; - } - -// INSERT POINT: _end - - if ( newX != this.x || newY != this.y ) { - // change easing function when scroller goes out of the boundaries - if ( newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY ) { - easing = utils.ease.quadratic; - } - - this.scrollTo(newX, newY, time, easing); - return; - } - - this._execEvent('scrollEnd'); - }, - - _resize: function () { - var that = this; - - clearTimeout(this.resizeTimeout); - - this.resizeTimeout = setTimeout(function () { - that.refresh(); - }, this.options.resizePolling); - }, - - resetPosition: function (time) { - var x = this.x, - y = this.y; - - time = time || 0; - - if ( !this.hasHorizontalScroll || this.x > 0 ) { - x = 0; - } else if ( this.x < this.maxScrollX ) { - x = this.maxScrollX; - } - - if ( !this.hasVerticalScroll || this.y > 0 ) { - y = 0; - } else if ( this.y < this.maxScrollY ) { - y = this.maxScrollY; - } - - if ( x == this.x && y == this.y ) { - return false; - } - - this.scrollTo(x, y, time, this.options.bounceEasing); - - return true; - }, - - disable: function () { - this.enabled = false; - }, - - enable: function () { - this.enabled = true; - }, - - refresh: function () { - utils.getRect(this.wrapper); // Force reflow - - this.wrapperWidth = this.wrapper.clientWidth; - this.wrapperHeight = this.wrapper.clientHeight; - - var rect = utils.getRect(this.scroller); -/* REPLACE START: refresh */ - - this.scrollerWidth = rect.width; - this.scrollerHeight = rect.height; - - this.maxScrollX = this.wrapperWidth - this.scrollerWidth; - this.maxScrollY = this.wrapperHeight - this.scrollerHeight; - -/* REPLACE END: refresh */ - - this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0; - this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0; - - if ( !this.hasHorizontalScroll ) { - this.maxScrollX = 0; - this.scrollerWidth = this.wrapperWidth; - } - - if ( !this.hasVerticalScroll ) { - this.maxScrollY = 0; - this.scrollerHeight = this.wrapperHeight; - } - - this.endTime = 0; - this.directionX = 0; - this.directionY = 0; - - if(utils.hasPointer && !this.options.disablePointer) { - // The wrapper should have `touchAction` property for using pointerEvent. - this.wrapper.style[utils.style.touchAction] = utils.getTouchAction(this.options.eventPassthrough, true); - - // case. not support 'pinch-zoom' - // https://github.com/cubiq/iscroll/issues/1118#issuecomment-270057583 - if (!this.wrapper.style[utils.style.touchAction]) { - this.wrapper.style[utils.style.touchAction] = utils.getTouchAction(this.options.eventPassthrough, false); - } - } - this.wrapperOffset = utils.offset(this.wrapper); - - this._execEvent('refresh'); - - this.resetPosition(); - -// INSERT POINT: _refresh - - }, - - on: function (type, fn) { - if ( !this._events[type] ) { - this._events[type] = []; - } - - this._events[type].push(fn); - }, - - off: function (type, fn) { - if ( !this._events[type] ) { - return; - } - - var index = this._events[type].indexOf(fn); - - if ( index > -1 ) { - this._events[type].splice(index, 1); - } - }, - - _execEvent: function (type) { - if ( !this._events[type] ) { - return; - } - - var i = 0, - l = this._events[type].length; - - if ( !l ) { - return; - } - - for ( ; i < l; i++ ) { - this._events[type][i].apply(this, [].slice.call(arguments, 1)); - } - }, - - scrollBy: function (x, y, time, easing) { - x = this.x + x; - y = this.y + y; - time = time || 0; - - this.scrollTo(x, y, time, easing); - }, - - scrollTo: function (x, y, time, easing) { - easing = easing || utils.ease.circular; - - this.isInTransition = this.options.useTransition && time > 0; - var transitionType = this.options.useTransition && easing.style; - if ( !time || transitionType ) { - if(transitionType) { - this._transitionTimingFunction(easing.style); - this._transitionTime(time); - } - this._translate(x, y); - } else { - this._animate(x, y, time, easing.fn); - } - }, - - scrollToElement: function (el, time, offsetX, offsetY, easing) { - el = el.nodeType ? el : this.scroller.querySelector(el); - - if ( !el ) { - return; - } - - var pos = utils.offset(el); - - pos.left -= this.wrapperOffset.left; - pos.top -= this.wrapperOffset.top; - - // if offsetX/Y are true we center the element to the screen - var elRect = utils.getRect(el); - var wrapperRect = utils.getRect(this.wrapper); - if ( offsetX === true ) { - offsetX = Math.round(elRect.width / 2 - wrapperRect.width / 2); - } - if ( offsetY === true ) { - offsetY = Math.round(elRect.height / 2 - wrapperRect.height / 2); - } - - pos.left -= offsetX || 0; - pos.top -= offsetY || 0; - - pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left; - pos.top = pos.top > 0 ? 0 : pos.top < this.maxScrollY ? this.maxScrollY : pos.top; - - time = time === undefined || time === null || time === 'auto' ? Math.max(Math.abs(this.x-pos.left), Math.abs(this.y-pos.top)) : time; - - this.scrollTo(pos.left, pos.top, time, easing); - }, - - _transitionTime: function (time) { - if (!this.options.useTransition) { - return; - } - time = time || 0; - var durationProp = utils.style.transitionDuration; - if(!durationProp) { - return; - } - - this.scrollerStyle[durationProp] = time + 'ms'; - - if ( !time && utils.isBadAndroid ) { - this.scrollerStyle[durationProp] = '0.0001ms'; - // remove 0.0001ms - var self = this; - rAF(function() { - if(self.scrollerStyle[durationProp] === '0.0001ms') { - self.scrollerStyle[durationProp] = '0s'; - } - }); - } - - - if ( this.indicators ) { - for ( var i = this.indicators.length; i--; ) { - this.indicators[i].transitionTime(time); - } - } - - -// INSERT POINT: _transitionTime - - }, - - _transitionTimingFunction: function (easing) { - this.scrollerStyle[utils.style.transitionTimingFunction] = easing; - - - if ( this.indicators ) { - for ( var i = this.indicators.length; i--; ) { - this.indicators[i].transitionTimingFunction(easing); - } - } - - -// INSERT POINT: _transitionTimingFunction - - }, - - _translate: function (x, y) { - if ( this.options.useTransform ) { - -/* REPLACE START: _translate */ - - this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; - -/* REPLACE END: _translate */ - - } else { - x = Math.round(x); - y = Math.round(y); - this.scrollerStyle.left = x + 'px'; - this.scrollerStyle.top = y + 'px'; - } - - this.x = x; - this.y = y; - - - if ( this.indicators ) { - for ( var i = this.indicators.length; i--; ) { - this.indicators[i].updatePosition(); - } - } - - -// INSERT POINT: _translate - - }, - - _initEvents: function (remove) { - var eventType = remove ? utils.removeEvent : utils.addEvent, - target = this.options.bindToWrapper ? this.wrapper : window; - - eventType(window, 'orientationchange', this); - eventType(window, 'resize', this); - - if ( this.options.click ) { - eventType(this.wrapper, 'click', this, true); - } - - if ( !this.options.disableMouse ) { - eventType(this.wrapper, 'mousedown', this); - eventType(target, 'mousemove', this); - eventType(target, 'mousecancel', this); - eventType(target, 'mouseup', this); - } - - if ( utils.hasPointer && !this.options.disablePointer ) { - eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this); - eventType(target, utils.prefixPointerEvent('pointermove'), this); - eventType(target, utils.prefixPointerEvent('pointercancel'), this); - eventType(target, utils.prefixPointerEvent('pointerup'), this); - } - - if ( utils.hasTouch && !this.options.disableTouch ) { - eventType(this.wrapper, 'touchstart', this); - eventType(target, 'touchmove', this); - eventType(target, 'touchcancel', this); - eventType(target, 'touchend', this); - } - - eventType(this.scroller, 'transitionend', this); - eventType(this.scroller, 'webkitTransitionEnd', this); - eventType(this.scroller, 'oTransitionEnd', this); - eventType(this.scroller, 'MSTransitionEnd', this); - }, - - getComputedPosition: function () { - var matrix = window.getComputedStyle(this.scroller, null), - x, y; - - if ( this.options.useTransform ) { - matrix = matrix[utils.style.transform].split(')')[0].split(', '); - x = +(matrix[12] || matrix[4]); - y = +(matrix[13] || matrix[5]); - } else { - x = +matrix.left.replace(/[^-\d.]/g, ''); - y = +matrix.top.replace(/[^-\d.]/g, ''); - } - - return { x: x, y: y }; - }, - _initIndicators: function () { - var interactive = this.options.interactiveScrollbars, - customStyle = typeof this.options.scrollbars != 'string', - indicators = [], - indicator; - - var that = this; - - this.indicators = []; - - if ( this.options.scrollbars ) { - // Vertical scrollbar - if ( this.options.scrollY ) { - indicator = { - el: createDefaultScrollbar('v', interactive, this.options.scrollbars), - interactive: interactive, - defaultScrollbars: true, - customStyle: customStyle, - resize: this.options.resizeScrollbars, - shrink: this.options.shrinkScrollbars, - fade: this.options.fadeScrollbars, - listenX: false - }; - - this.wrapper.appendChild(indicator.el); - indicators.push(indicator); - } - - // Horizontal scrollbar - if ( this.options.scrollX ) { - indicator = { - el: createDefaultScrollbar('h', interactive, this.options.scrollbars), - interactive: interactive, - defaultScrollbars: true, - customStyle: customStyle, - resize: this.options.resizeScrollbars, - shrink: this.options.shrinkScrollbars, - fade: this.options.fadeScrollbars, - listenY: false - }; - - this.wrapper.appendChild(indicator.el); - indicators.push(indicator); - } - } - - if ( this.options.indicators ) { - // TODO: check concat compatibility - indicators = indicators.concat(this.options.indicators); - } - - for ( var i = indicators.length; i--; ) { - this.indicators.push( new Indicator(this, indicators[i]) ); - } - - // TODO: check if we can use array.map (wide compatibility and performance issues) - function _indicatorsMap (fn) { - if (that.indicators) { - for ( var i = that.indicators.length; i--; ) { - fn.call(that.indicators[i]); - } - } - } - - if ( this.options.fadeScrollbars ) { - this.on('scrollEnd', function () { - _indicatorsMap(function () { - this.fade(); - }); - }); - - this.on('scrollCancel', function () { - _indicatorsMap(function () { - this.fade(); - }); - }); - - this.on('scrollStart', function () { - _indicatorsMap(function () { - this.fade(1); - }); - }); - - this.on('beforeScrollStart', function () { - _indicatorsMap(function () { - this.fade(1, true); - }); - }); - } - - - this.on('refresh', function () { - _indicatorsMap(function () { - this.refresh(); - }); - }); - - this.on('destroy', function () { - _indicatorsMap(function () { - this.destroy(); - }); - - delete this.indicators; - }); - }, - - _initWheel: function () { - utils.addEvent(this.wrapper, 'wheel', this); - utils.addEvent(this.wrapper, 'mousewheel', this); - utils.addEvent(this.wrapper, 'DOMMouseScroll', this); - - this.on('destroy', function () { - clearTimeout(this.wheelTimeout); - this.wheelTimeout = null; - utils.removeEvent(this.wrapper, 'wheel', this); - utils.removeEvent(this.wrapper, 'mousewheel', this); - utils.removeEvent(this.wrapper, 'DOMMouseScroll', this); - }); - }, - - _wheel: function (e) { - if ( !this.enabled ) { - return; - } - - e.preventDefault(); - - var wheelDeltaX, wheelDeltaY, - newX, newY, - that = this; - - if ( this.wheelTimeout === undefined ) { - that._execEvent('scrollStart'); - } - - // Execute the scrollEnd event after 400ms the wheel stopped scrolling - clearTimeout(this.wheelTimeout); - this.wheelTimeout = setTimeout(function () { - if(!that.options.snap) { - that._execEvent('scrollEnd'); - } - that.wheelTimeout = undefined; - }, 400); - - if ( 'deltaX' in e ) { - if (e.deltaMode === 1) { - wheelDeltaX = -e.deltaX * this.options.mouseWheelSpeed; - wheelDeltaY = -e.deltaY * this.options.mouseWheelSpeed; - } else { - wheelDeltaX = -e.deltaX; - wheelDeltaY = -e.deltaY; - } - } else if ( 'wheelDeltaX' in e ) { - wheelDeltaX = e.wheelDeltaX / 120 * this.options.mouseWheelSpeed; - wheelDeltaY = e.wheelDeltaY / 120 * this.options.mouseWheelSpeed; - } else if ( 'wheelDelta' in e ) { - wheelDeltaX = wheelDeltaY = e.wheelDelta / 120 * this.options.mouseWheelSpeed; - } else if ( 'detail' in e ) { - wheelDeltaX = wheelDeltaY = -e.detail / 3 * this.options.mouseWheelSpeed; - } else { - return; - } - - wheelDeltaX *= this.options.invertWheelDirection; - wheelDeltaY *= this.options.invertWheelDirection; - - if ( !this.hasVerticalScroll ) { - wheelDeltaX = wheelDeltaY; - wheelDeltaY = 0; - } - - if ( this.options.snap ) { - newX = this.currentPage.pageX; - newY = this.currentPage.pageY; - - if ( wheelDeltaX > 0 ) { - newX--; - } else if ( wheelDeltaX < 0 ) { - newX++; - } - - if ( wheelDeltaY > 0 ) { - newY--; - } else if ( wheelDeltaY < 0 ) { - newY++; - } - - this.goToPage(newX, newY); - - return; - } - - newX = this.x + Math.round(this.hasHorizontalScroll ? wheelDeltaX : 0); - newY = this.y + Math.round(this.hasVerticalScroll ? wheelDeltaY : 0); - - this.directionX = wheelDeltaX > 0 ? -1 : wheelDeltaX < 0 ? 1 : 0; - this.directionY = wheelDeltaY > 0 ? -1 : wheelDeltaY < 0 ? 1 : 0; - - if ( newX > 0 ) { - newX = 0; - } else if ( newX < this.maxScrollX ) { - newX = this.maxScrollX; - } - - if ( newY > 0 ) { - newY = 0; - } else if ( newY < this.maxScrollY ) { - newY = this.maxScrollY; - } - - this.scrollTo(newX, newY, 0); - - if ( this.options.probeType > 1 ) { - this._execEvent('scroll'); - } - -// INSERT POINT: _wheel - }, - - _initSnap: function () { - this.currentPage = {}; - - if ( typeof this.options.snap == 'string' ) { - this.options.snap = this.scroller.querySelectorAll(this.options.snap); - } - - this.on('refresh', function () { - var i = 0, l, - m = 0, n, - cx, cy, - x = 0, y, - stepX = this.options.snapStepX || this.wrapperWidth, - stepY = this.options.snapStepY || this.wrapperHeight, - el, - rect; - - this.pages = []; - - if ( !this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight ) { - return; - } - - if ( this.options.snap === true ) { - cx = Math.round( stepX / 2 ); - cy = Math.round( stepY / 2 ); - - while ( x > -this.scrollerWidth ) { - this.pages[i] = []; - l = 0; - y = 0; - - while ( y > -this.scrollerHeight ) { - this.pages[i][l] = { - x: Math.max(x, this.maxScrollX), - y: Math.max(y, this.maxScrollY), - width: stepX, - height: stepY, - cx: x - cx, - cy: y - cy - }; - - y -= stepY; - l++; - } - - x -= stepX; - i++; - } - } else { - el = this.options.snap; - l = el.length; - n = -1; - - for ( ; i < l; i++ ) { - rect = utils.getRect(el[i]); - if ( i === 0 || rect.left <= utils.getRect(el[i-1]).left ) { - m = 0; - n++; - } - - if ( !this.pages[m] ) { - this.pages[m] = []; - } - - x = Math.max(-rect.left, this.maxScrollX); - y = Math.max(-rect.top, this.maxScrollY); - cx = x - Math.round(rect.width / 2); - cy = y - Math.round(rect.height / 2); - - this.pages[m][n] = { - x: x, - y: y, - width: rect.width, - height: rect.height, - cx: cx, - cy: cy - }; - - if ( x > this.maxScrollX ) { - m++; - } - } - } - - this.goToPage(this.currentPage.pageX || 0, this.currentPage.pageY || 0, 0); - - // Update snap threshold if needed - if ( this.options.snapThreshold % 1 === 0 ) { - this.snapThresholdX = this.options.snapThreshold; - this.snapThresholdY = this.options.snapThreshold; - } else { - this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * this.options.snapThreshold); - this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * this.options.snapThreshold); - } - }); - - this.on('flick', function () { - var time = this.options.snapSpeed || Math.max( - Math.max( - Math.min(Math.abs(this.x - this.startX), 1000), - Math.min(Math.abs(this.y - this.startY), 1000) - ), 300); - - this.goToPage( - this.currentPage.pageX + this.directionX, - this.currentPage.pageY + this.directionY, - time - ); - }); - }, - - _nearestSnap: function (x, y) { - if ( !this.pages.length ) { - return { x: 0, y: 0, pageX: 0, pageY: 0 }; - } - - var i = 0, - l = this.pages.length, - m = 0; - - // Check if we exceeded the snap threshold - if ( Math.abs(x - this.absStartX) < this.snapThresholdX && - Math.abs(y - this.absStartY) < this.snapThresholdY ) { - return this.currentPage; - } - - if ( x > 0 ) { - x = 0; - } else if ( x < this.maxScrollX ) { - x = this.maxScrollX; - } - - if ( y > 0 ) { - y = 0; - } else if ( y < this.maxScrollY ) { - y = this.maxScrollY; - } - - for ( ; i < l; i++ ) { - if ( x >= this.pages[i][0].cx ) { - x = this.pages[i][0].x; - break; - } - } - - l = this.pages[i].length; - - for ( ; m < l; m++ ) { - if ( y >= this.pages[0][m].cy ) { - y = this.pages[0][m].y; - break; - } - } - - if ( i == this.currentPage.pageX ) { - i += this.directionX; - - if ( i < 0 ) { - i = 0; - } else if ( i >= this.pages.length ) { - i = this.pages.length - 1; - } - - x = this.pages[i][0].x; - } - - if ( m == this.currentPage.pageY ) { - m += this.directionY; - - if ( m < 0 ) { - m = 0; - } else if ( m >= this.pages[0].length ) { - m = this.pages[0].length - 1; - } - - y = this.pages[0][m].y; - } - - return { - x: x, - y: y, - pageX: i, - pageY: m - }; - }, - - goToPage: function (x, y, time, easing) { - easing = easing || this.options.bounceEasing; - - if ( x >= this.pages.length ) { - x = this.pages.length - 1; - } else if ( x < 0 ) { - x = 0; - } - - if ( y >= this.pages[x].length ) { - y = this.pages[x].length - 1; - } else if ( y < 0 ) { - y = 0; - } - - var posX = this.pages[x][y].x, - posY = this.pages[x][y].y; - - time = time === undefined ? this.options.snapSpeed || Math.max( - Math.max( - Math.min(Math.abs(posX - this.x), 1000), - Math.min(Math.abs(posY - this.y), 1000) - ), 300) : time; - - this.currentPage = { - x: posX, - y: posY, - pageX: x, - pageY: y - }; - - this.scrollTo(posX, posY, time, easing); - }, - - next: function (time, easing) { - var x = this.currentPage.pageX, - y = this.currentPage.pageY; - - x++; - - if ( x >= this.pages.length && this.hasVerticalScroll ) { - x = 0; - y++; - } - - this.goToPage(x, y, time, easing); - }, - - prev: function (time, easing) { - var x = this.currentPage.pageX, - y = this.currentPage.pageY; - - x--; - - if ( x < 0 && this.hasVerticalScroll ) { - x = 0; - y--; - } - - this.goToPage(x, y, time, easing); - }, - - _initKeys: function (e) { - // default key bindings - var keys = { - pageUp: 33, - pageDown: 34, - end: 35, - home: 36, - left: 37, - up: 38, - right: 39, - down: 40 - }; - var i; - - // if you give me characters I give you keycode - if ( typeof this.options.keyBindings == 'object' ) { - for ( i in this.options.keyBindings ) { - if ( typeof this.options.keyBindings[i] == 'string' ) { - this.options.keyBindings[i] = this.options.keyBindings[i].toUpperCase().charCodeAt(0); - } - } - } else { - this.options.keyBindings = {}; - } - - for ( i in keys ) { - this.options.keyBindings[i] = this.options.keyBindings[i] || keys[i]; - } - - utils.addEvent(window, 'keydown', this); - - this.on('destroy', function () { - utils.removeEvent(window, 'keydown', this); - }); - }, - - _key: function (e) { - if ( !this.enabled ) { - return; - } - - var snap = this.options.snap, // we are using this alot, better to cache it - newX = snap ? this.currentPage.pageX : this.x, - newY = snap ? this.currentPage.pageY : this.y, - now = utils.getTime(), - prevTime = this.keyTime || 0, - acceleration = 0.250, - pos; - - if ( this.options.useTransition && this.isInTransition ) { - pos = this.getComputedPosition(); - - this._translate(Math.round(pos.x), Math.round(pos.y)); - this.isInTransition = false; - } - - this.keyAcceleration = now - prevTime < 200 ? Math.min(this.keyAcceleration + acceleration, 50) : 0; - - switch ( e.keyCode ) { - case this.options.keyBindings.pageUp: - if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) { - newX += snap ? 1 : this.wrapperWidth; - } else { - newY += snap ? 1 : this.wrapperHeight; - } - break; - case this.options.keyBindings.pageDown: - if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) { - newX -= snap ? 1 : this.wrapperWidth; - } else { - newY -= snap ? 1 : this.wrapperHeight; - } - break; - case this.options.keyBindings.end: - newX = snap ? this.pages.length-1 : this.maxScrollX; - newY = snap ? this.pages[0].length-1 : this.maxScrollY; - break; - case this.options.keyBindings.home: - newX = 0; - newY = 0; - break; - case this.options.keyBindings.left: - newX += snap ? -1 : 5 + this.keyAcceleration>>0; - break; - case this.options.keyBindings.up: - newY += snap ? 1 : 5 + this.keyAcceleration>>0; - break; - case this.options.keyBindings.right: - newX -= snap ? -1 : 5 + this.keyAcceleration>>0; - break; - case this.options.keyBindings.down: - newY -= snap ? 1 : 5 + this.keyAcceleration>>0; - break; - default: - return; - } - - if ( snap ) { - this.goToPage(newX, newY); - return; - } - - if ( newX > 0 ) { - newX = 0; - this.keyAcceleration = 0; - } else if ( newX < this.maxScrollX ) { - newX = this.maxScrollX; - this.keyAcceleration = 0; - } - - if ( newY > 0 ) { - newY = 0; - this.keyAcceleration = 0; - } else if ( newY < this.maxScrollY ) { - newY = this.maxScrollY; - this.keyAcceleration = 0; - } - - this.scrollTo(newX, newY, 0); - - this.keyTime = now; - }, - - _animate: function (destX, destY, duration, easingFn) { - var that = this, - startX = this.x, - startY = this.y, - startTime = utils.getTime(), - destTime = startTime + duration; - - function step () { - var now = utils.getTime(), - newX, newY, - easing; - - if ( now >= destTime ) { - that.isAnimating = false; - that._translate(destX, destY); - - if ( !that.resetPosition(that.options.bounceTime) ) { - that._execEvent('scrollEnd'); - } - - return; - } - - now = ( now - startTime ) / duration; - easing = easingFn(now); - newX = ( destX - startX ) * easing + startX; - newY = ( destY - startY ) * easing + startY; - that._translate(newX, newY); - - if ( that.isAnimating ) { - rAF(step); - } - - if ( that.options.probeType == 3 ) { - that._execEvent('scroll'); - } - } - - this.isAnimating = true; - step(); - }, - - handleEvent: function (e) { - switch ( e.type ) { - case 'touchstart': - case 'pointerdown': - case 'MSPointerDown': - case 'mousedown': - this._start(e); - break; - case 'touchmove': - case 'pointermove': - case 'MSPointerMove': - case 'mousemove': - this._move(e); - break; - case 'touchend': - case 'pointerup': - case 'MSPointerUp': - case 'mouseup': - case 'touchcancel': - case 'pointercancel': - case 'MSPointerCancel': - case 'mousecancel': - this._end(e); - break; - case 'orientationchange': - case 'resize': - this._resize(); - break; - case 'transitionend': - case 'webkitTransitionEnd': - case 'oTransitionEnd': - case 'MSTransitionEnd': - this._transitionEnd(e); - break; - case 'wheel': - case 'DOMMouseScroll': - case 'mousewheel': - this._wheel(e); - break; - case 'keydown': - this._key(e); - break; - case 'click': - if ( this.enabled && !e._constructed ) { - e.preventDefault(); - e.stopPropagation(); - } - break; - } - } -}; -function createDefaultScrollbar (direction, interactive, type) { - var scrollbar = document.createElement('div'), - indicator = document.createElement('div'); - - if ( type === true ) { - scrollbar.style.cssText = 'position:absolute;z-index:9999'; - indicator.style.cssText = '-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px'; - } - - indicator.className = 'iScrollIndicator'; - - if ( direction == 'h' ) { - if ( type === true ) { - scrollbar.style.cssText += ';height:7px;left:2px;right:2px;bottom:0'; - indicator.style.height = '100%'; - } - scrollbar.className = 'iScrollHorizontalScrollbar'; - } else { - if ( type === true ) { - scrollbar.style.cssText += ';width:7px;bottom:2px;top:2px;right:1px'; - indicator.style.width = '100%'; - } - scrollbar.className = 'iScrollVerticalScrollbar'; - } - - scrollbar.style.cssText += ';overflow:hidden'; - - if ( !interactive ) { - scrollbar.style.pointerEvents = 'none'; - } - - scrollbar.appendChild(indicator); - - return scrollbar; -} - -function Indicator (scroller, options) { - this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el; - this.wrapperStyle = this.wrapper.style; - this.indicator = this.wrapper.children[0]; - this.indicatorStyle = this.indicator.style; - this.scroller = scroller; - - this.options = { - listenX: true, - listenY: true, - interactive: false, - resize: true, - defaultScrollbars: false, - shrink: false, - fade: false, - speedRatioX: 0, - speedRatioY: 0 - }; - - for ( var i in options ) { - this.options[i] = options[i]; - } - - this.sizeRatioX = 1; - this.sizeRatioY = 1; - this.maxPosX = 0; - this.maxPosY = 0; - - if ( this.options.interactive ) { - if ( !this.options.disableTouch ) { - utils.addEvent(this.indicator, 'touchstart', this); - utils.addEvent(window, 'touchend', this); - } - if ( !this.options.disablePointer ) { - utils.addEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); - utils.addEvent(window, utils.prefixPointerEvent('pointerup'), this); - } - if ( !this.options.disableMouse ) { - utils.addEvent(this.indicator, 'mousedown', this); - utils.addEvent(window, 'mouseup', this); - } - } - - if ( this.options.fade ) { - this.wrapperStyle[utils.style.transform] = this.scroller.translateZ; - var durationProp = utils.style.transitionDuration; - if(!durationProp) { - return; - } - this.wrapperStyle[durationProp] = utils.isBadAndroid ? '0.0001ms' : '0ms'; - // remove 0.0001ms - var self = this; - if(utils.isBadAndroid) { - rAF(function() { - if(self.wrapperStyle[durationProp] === '0.0001ms') { - self.wrapperStyle[durationProp] = '0s'; - } - }); - } - this.wrapperStyle.opacity = '0'; - } -} - -Indicator.prototype = { - handleEvent: function (e) { - switch ( e.type ) { - case 'touchstart': - case 'pointerdown': - case 'MSPointerDown': - case 'mousedown': - this._start(e); - break; - case 'touchmove': - case 'pointermove': - case 'MSPointerMove': - case 'mousemove': - this._move(e); - break; - case 'touchend': - case 'pointerup': - case 'MSPointerUp': - case 'mouseup': - case 'touchcancel': - case 'pointercancel': - case 'MSPointerCancel': - case 'mousecancel': - this._end(e); - break; - } - }, - - destroy: function () { - if ( this.options.fadeScrollbars ) { - clearTimeout(this.fadeTimeout); - this.fadeTimeout = null; - } - if ( this.options.interactive ) { - utils.removeEvent(this.indicator, 'touchstart', this); - utils.removeEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); - utils.removeEvent(this.indicator, 'mousedown', this); - - utils.removeEvent(window, 'touchmove', this); - utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); - utils.removeEvent(window, 'mousemove', this); - - utils.removeEvent(window, 'touchend', this); - utils.removeEvent(window, utils.prefixPointerEvent('pointerup'), this); - utils.removeEvent(window, 'mouseup', this); - } - - if ( this.options.defaultScrollbars && this.wrapper.parentNode ) { - this.wrapper.parentNode.removeChild(this.wrapper); - } - }, - - _start: function (e) { - var point = e.touches ? e.touches[0] : e; - - e.preventDefault(); - e.stopPropagation(); - - this.transitionTime(); - - this.initiated = true; - this.moved = false; - this.lastPointX = point.pageX; - this.lastPointY = point.pageY; - - this.startTime = utils.getTime(); - - if ( !this.options.disableTouch ) { - utils.addEvent(window, 'touchmove', this); - } - if ( !this.options.disablePointer ) { - utils.addEvent(window, utils.prefixPointerEvent('pointermove'), this); - } - if ( !this.options.disableMouse ) { - utils.addEvent(window, 'mousemove', this); - } - - this.scroller._execEvent('beforeScrollStart'); - }, - - _move: function (e) { - var point = e.touches ? e.touches[0] : e, - deltaX, deltaY, - newX, newY, - timestamp = utils.getTime(); - - if ( !this.moved ) { - this.scroller._execEvent('scrollStart'); - } - - this.moved = true; - - deltaX = point.pageX - this.lastPointX; - this.lastPointX = point.pageX; - - deltaY = point.pageY - this.lastPointY; - this.lastPointY = point.pageY; - - newX = this.x + deltaX; - newY = this.y + deltaY; - - this._pos(newX, newY); - - - if ( this.scroller.options.probeType == 1 && timestamp - this.startTime > 300 ) { - this.startTime = timestamp; - this.scroller._execEvent('scroll'); - } else if ( this.scroller.options.probeType > 1 ) { - this.scroller._execEvent('scroll'); - } - - -// INSERT POINT: indicator._move - - e.preventDefault(); - e.stopPropagation(); - }, - - _end: function (e) { - if ( !this.initiated ) { - return; - } - - this.initiated = false; - - e.preventDefault(); - e.stopPropagation(); - - utils.removeEvent(window, 'touchmove', this); - utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); - utils.removeEvent(window, 'mousemove', this); - - if ( this.scroller.options.snap ) { - var snap = this.scroller._nearestSnap(this.scroller.x, this.scroller.y); - - var time = this.options.snapSpeed || Math.max( - Math.max( - Math.min(Math.abs(this.scroller.x - snap.x), 1000), - Math.min(Math.abs(this.scroller.y - snap.y), 1000) - ), 300); - - if ( this.scroller.x != snap.x || this.scroller.y != snap.y ) { - this.scroller.directionX = 0; - this.scroller.directionY = 0; - this.scroller.currentPage = snap; - this.scroller.scrollTo(snap.x, snap.y, time, this.scroller.options.bounceEasing); - } - } - - if ( this.moved ) { - this.scroller._execEvent('scrollEnd'); - } - }, - - transitionTime: function (time) { - time = time || 0; - var durationProp = utils.style.transitionDuration; - if(!durationProp) { - return; - } - - this.indicatorStyle[durationProp] = time + 'ms'; - - if ( !time && utils.isBadAndroid ) { - this.indicatorStyle[durationProp] = '0.0001ms'; - // remove 0.0001ms - var self = this; - rAF(function() { - if(self.indicatorStyle[durationProp] === '0.0001ms') { - self.indicatorStyle[durationProp] = '0s'; - } - }); - } - }, - - transitionTimingFunction: function (easing) { - this.indicatorStyle[utils.style.transitionTimingFunction] = easing; - }, - - refresh: function () { - this.transitionTime(); - - if ( this.options.listenX && !this.options.listenY ) { - this.indicatorStyle.display = this.scroller.hasHorizontalScroll ? 'block' : 'none'; - } else if ( this.options.listenY && !this.options.listenX ) { - this.indicatorStyle.display = this.scroller.hasVerticalScroll ? 'block' : 'none'; - } else { - this.indicatorStyle.display = this.scroller.hasHorizontalScroll || this.scroller.hasVerticalScroll ? 'block' : 'none'; - } - - if ( this.scroller.hasHorizontalScroll && this.scroller.hasVerticalScroll ) { - utils.addClass(this.wrapper, 'iScrollBothScrollbars'); - utils.removeClass(this.wrapper, 'iScrollLoneScrollbar'); - - if ( this.options.defaultScrollbars && this.options.customStyle ) { - if ( this.options.listenX ) { - this.wrapper.style.right = '8px'; - } else { - this.wrapper.style.bottom = '8px'; - } - } - } else { - utils.removeClass(this.wrapper, 'iScrollBothScrollbars'); - utils.addClass(this.wrapper, 'iScrollLoneScrollbar'); - - if ( this.options.defaultScrollbars && this.options.customStyle ) { - if ( this.options.listenX ) { - this.wrapper.style.right = '2px'; - } else { - this.wrapper.style.bottom = '2px'; - } - } - } - - utils.getRect(this.wrapper); // force refresh - - if ( this.options.listenX ) { - this.wrapperWidth = this.wrapper.clientWidth; - if ( this.options.resize ) { - this.indicatorWidth = Math.max(Math.round(this.wrapperWidth * this.wrapperWidth / (this.scroller.scrollerWidth || this.wrapperWidth || 1)), 8); - this.indicatorStyle.width = this.indicatorWidth + 'px'; - } else { - this.indicatorWidth = this.indicator.clientWidth; - } - - this.maxPosX = this.wrapperWidth - this.indicatorWidth; - - if ( this.options.shrink == 'clip' ) { - this.minBoundaryX = -this.indicatorWidth + 8; - this.maxBoundaryX = this.wrapperWidth - 8; - } else { - this.minBoundaryX = 0; - this.maxBoundaryX = this.maxPosX; - } - - this.sizeRatioX = this.options.speedRatioX || (this.scroller.maxScrollX && (this.maxPosX / this.scroller.maxScrollX)); - } - - if ( this.options.listenY ) { - this.wrapperHeight = this.wrapper.clientHeight; - if ( this.options.resize ) { - this.indicatorHeight = Math.max(Math.round(this.wrapperHeight * this.wrapperHeight / (this.scroller.scrollerHeight || this.wrapperHeight || 1)), 8); - this.indicatorStyle.height = this.indicatorHeight + 'px'; - } else { - this.indicatorHeight = this.indicator.clientHeight; - } - - this.maxPosY = this.wrapperHeight - this.indicatorHeight; - - if ( this.options.shrink == 'clip' ) { - this.minBoundaryY = -this.indicatorHeight + 8; - this.maxBoundaryY = this.wrapperHeight - 8; - } else { - this.minBoundaryY = 0; - this.maxBoundaryY = this.maxPosY; - } - - this.maxPosY = this.wrapperHeight - this.indicatorHeight; - this.sizeRatioY = this.options.speedRatioY || (this.scroller.maxScrollY && (this.maxPosY / this.scroller.maxScrollY)); - } - - this.updatePosition(); - }, - - updatePosition: function () { - var x = this.options.listenX && Math.round(this.sizeRatioX * this.scroller.x) || 0, - y = this.options.listenY && Math.round(this.sizeRatioY * this.scroller.y) || 0; - - if ( !this.options.ignoreBoundaries ) { - if ( x < this.minBoundaryX ) { - if ( this.options.shrink == 'scale' ) { - this.width = Math.max(this.indicatorWidth + x, 8); - this.indicatorStyle.width = this.width + 'px'; - } - x = this.minBoundaryX; - } else if ( x > this.maxBoundaryX ) { - if ( this.options.shrink == 'scale' ) { - this.width = Math.max(this.indicatorWidth - (x - this.maxPosX), 8); - this.indicatorStyle.width = this.width + 'px'; - x = this.maxPosX + this.indicatorWidth - this.width; - } else { - x = this.maxBoundaryX; - } - } else if ( this.options.shrink == 'scale' && this.width != this.indicatorWidth ) { - this.width = this.indicatorWidth; - this.indicatorStyle.width = this.width + 'px'; - } - - if ( y < this.minBoundaryY ) { - if ( this.options.shrink == 'scale' ) { - this.height = Math.max(this.indicatorHeight + y * 3, 8); - this.indicatorStyle.height = this.height + 'px'; - } - y = this.minBoundaryY; - } else if ( y > this.maxBoundaryY ) { - if ( this.options.shrink == 'scale' ) { - this.height = Math.max(this.indicatorHeight - (y - this.maxPosY) * 3, 8); - this.indicatorStyle.height = this.height + 'px'; - y = this.maxPosY + this.indicatorHeight - this.height; - } else { - y = this.maxBoundaryY; - } - } else if ( this.options.shrink == 'scale' && this.height != this.indicatorHeight ) { - this.height = this.indicatorHeight; - this.indicatorStyle.height = this.height + 'px'; - } - } - - this.x = x; - this.y = y; - - if ( this.scroller.options.useTransform ) { - this.indicatorStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.scroller.translateZ; - } else { - this.indicatorStyle.left = x + 'px'; - this.indicatorStyle.top = y + 'px'; - } - }, - - _pos: function (x, y) { - if ( x < 0 ) { - x = 0; - } else if ( x > this.maxPosX ) { - x = this.maxPosX; - } - - if ( y < 0 ) { - y = 0; - } else if ( y > this.maxPosY ) { - y = this.maxPosY; - } - - x = this.options.listenX ? Math.round(x / this.sizeRatioX) : this.scroller.x; - y = this.options.listenY ? Math.round(y / this.sizeRatioY) : this.scroller.y; - - this.scroller.scrollTo(x, y); - }, - - fade: function (val, hold) { - if ( hold && !this.visible ) { - return; - } - - clearTimeout(this.fadeTimeout); - this.fadeTimeout = null; - - var time = val ? 250 : 500, - delay = val ? 0 : 300; - - val = val ? '1' : '0'; - - this.wrapperStyle[utils.style.transitionDuration] = time + 'ms'; - - this.fadeTimeout = setTimeout((function (val) { - this.wrapperStyle.opacity = val; - this.visible = +val; - }).bind(this, val), delay); - } -}; - -IScroll.utils = utils; - -if ( typeof module != 'undefined' && module.exports ) { - module.exports = IScroll; -} else if ( typeof define == 'function' && define.amd ) { - define( function () { return IScroll; } ); -} else { - window.IScroll = IScroll; -} - -})(window, document, Math); diff --git a/web_responsive/static/lib/js/jquery.sendkeys.4.js b/web_responsive/static/lib/js/jquery.sendkeys.4.js deleted file mode 100644 index 5c8cd23cc..000000000 --- a/web_responsive/static/lib/js/jquery.sendkeys.4.js +++ /dev/null @@ -1,57 +0,0 @@ -// insert characters in a textarea or text input field -// special characters are enclosed in {}; use {{} for the { character itself -// documentation: http://bililite.com/blog/2008/08/20/the-fnsendkeys-plugin/ -// source: https://github.com/dwachss/bililiteRange/blob/master/jquery.sendkeys.js -// Version: 4 -// Copyright (c) 2013 Daniel Wachsstock -// MIT license: -// 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. - -(function($){ - -$.fn.sendkeys = function (x){ - x = x.replace(/([^{])\n/g, '$1{enter}'); // turn line feeds into explicit break insertions, but not if escaped - return this.each( function(){ - bililiteRange(this).bounds('selection').sendkeys(x).select(); - this.focus(); - }); -}; // sendkeys - -// add a default handler for keydowns so that we can send keystrokes, even though code-generated events -// are untrusted (http://www.w3.org/TR/DOM-Level-3-Events/#trusted-events) -// documentation of special event handlers is at http://learn.jquery.com/events/event-extensions/ -$.event.special.keydown = $.event.special.keydown || {}; -$.event.special.keydown._default = function (evt){ - if (evt.isTrusted) return false; - if (evt.ctrlKey || evt.altKey || evt.metaKey) return false; // only deal with printable characters. This may be a false assumption - if (evt.key == null) return false; // nothing to print. Use the keymap plugin to set this - var target = evt.target; - if (target.isContentEditable || target.nodeName == 'INPUT' || target.nodeName == 'TEXTAREA') { - // only insert into editable elements - var key = evt.key; - if (key.length > 1 && key.charAt(0) != '{') key = '{'+key+'}'; // sendkeys notation - $(target).sendkeys(key); - return true; - } - return false; -} -})(jQuery) diff --git a/web_responsive/static/src/css/web_responsive.scss b/web_responsive/static/src/css/web_responsive.scss new file mode 100644 index 000000000..85c089cc8 --- /dev/null +++ b/web_responsive/static/src/css/web_responsive.scss @@ -0,0 +1,427 @@ +/* Copyright 2018 Tecnativa - Jairo Llopis + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ + +@mixin full-screen-dropdown { + border: none; + box-shadow: none; + display: flex; + flex-direction: column; + height: calc(100vh - #{$o-navbar-height}); + max-height: calc(100vh - #{$o-navbar-height}); + position: fixed; + width: 100vw; + z-index: 100; + // Inline style will override our `top`, so we need !important here + top: $o-navbar-height !important; + transform: none !important; +} + +// Main navbar (with apps menu, user menu, debug menu...) +@include media-breakpoint-down(sm) { + .o_main_navbar { + // This allows to have a sane layout for mobiles + display: flex; + + // Remove clutter to only have small icons that fit in a small screen + > .dropdown { + display: flex; + + .navbar-toggler { + color: white; + } + + .o_menu_sections, + .o_menu_systray, + { + padding: 0; + } + } + + // Whitespace before systray icons + .o_menu_systray { + margin-left: auto; + } + + // Hide big things + .o_menu_brand, + .o_menu_sections, + .oe_topbar_name, + { + display: none; + } + + // Fix toggler button padding + .o-menu-toggle { + cursor: pointer; + padding: 0 $o-horizontal-padding; + } + + // Custom fullscreen layout when showing submenus + .o_menu_sections.show { + @include full-screen-dropdown(); + background-color: $dropdown-bg; + + .show { + display: flex; + flex-direction: column; + + .dropdown-menu { + margin-left: 1rem; + min-width: auto; + width: calc(100vw - 2rem); + } + } + + > li, + .o_menu_entry_lvl_1, + .o_menu_header_lvl_1, + { + // Homogeneous background color + background-color: $dropdown-bg; + color: $dropdown-link-color; + + &:hover { + background-color: $dropdown-link-hover-bg; + color: $dropdown-link-hover-color; + } + + // Disable the .o-no-caret class effect, to display the caret + &.o-no-caret::after { + content: ""; + } + + // Fix a strange glitch leaving headers invisible + .dropdown-header { + color: $dropdown-header-color; + } + } + } + + // Custom fullscreen layout for systray items + .o_mail_systray_dropdown.show { + @include full-screen-dropdown(); + + // Fix stretchy images + .o_mail_preview_image { + align-items: center; + display: flex; + flex-direction: row; + + img { + display: block; + height: auto; + } + } + } + + // Higher height for dropdown items, for those with sausage fingers + .dropdown-menu .dropdown-item { + padding: { + bottom: 0.5rem; + top: 0.5rem; + } + } + } +} + +// Iconized full screen apps menu +.o_menu_apps { + + .search-input:focus { + border-color: $o-brand-primary; + } + + .dropdown-menu.show { + @include full-screen-dropdown(); + + // Display apps in a grid + align-content: flex-start; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + + @include media-breakpoint-up(lg) { + padding: { + left: 20vw; + right: 20vw; + } + } + + .o_app { + align-items: center; + display: flex; + flex-direction: column; + justify-content: flex-start; + + // Size depends on screen + width: 33.33333333%; + @include media-breakpoint-up(sm) { + width: 25%; + } + @include media-breakpoint-up(md) { + width: 16.6666666%; + } + } + + // Hide app icons when searching + .has-results ~ .o_app { + display: none; + } + + .o-app-icon { + height: auto; + max-width: 7rem; + width: 100%; + } + + // Search input for menus + .form-row { + width: 100%; + } + + .o-menu-search-result { + align-items: center; + background-position: left; + background-repeat: no-repeat; + background-size: contain; + cursor: pointer; + display: flex; + padding-left: 3rem; + white-space: normal; + } + + // Allow to scroll only on results, keeping static search box above + .search-container.has-results { + height: 100%; + + .search-input { + height: 3em; + } + + .search-results { + height: calc(100% - 3em); + overflow: auto; + } + } + } +} + +// Scroll all but top bar +html .o_web_client .o_main .o_main_content { + @include media-breakpoint-down(sm) { + overflow: auto; + + .o_content { + overflow: initial; + } + } +} + +// Control panel (breadcrumbs, search box, buttons...) +@include media-breakpoint-down(sm) { + .o_control_panel { + // Arrange buttons to use space better + .breadcrumb, + .o_cp_buttons, + .o_cp_left, + .o_cp_right, + .o_cp_searchview, + { + flex: 1 1 100%; + @include media-breakpoint-up(md) { + flex-basis: 50%; + } + } + + .breadcrumb { + flex-basis: 80%; + } + + .o_cp_searchview, + .o_cp_right, + { + flex-basis: 10%; + } + + .o_cp_left { + flex-basis: 50%; + white-space: nowrap; + } + + .o_cp_pager { + white-space: nowrap; + } + + // Hide all but 2 last breadcrumbs, and render 2nd-to-last as arrow + .breadcrumb-item { + &:not(.active) { + padding-left: 0; + } + + &::before { + content: none; + padding-right: 0; + } + + &:nth-last-of-type(1n+3) { + display: none; + } + + &:nth-last-of-type(2) { + &::before { + color: var(--primary); + content: "\f048"; // .fa-step-backward + cursor: pointer; + font-family: FontAwesome; + } + + a { + display: none; + } + } + } + + // Ellipsize long breadcrumbs + .breadcrumb { + max-width: 100%; + text-overflow: ellipsis; + } + + // Empty sidebar should not break layout + .o_cp_sidebar:blank { + display: none; + } + + // In case you install `mail`, there is a mess on BS vs inline styles + // we need to fix + .o_cp_buttons .btn.d-block:not(.d-none) { + display: inline-block !important; + } + + // Dropdown with buttons to switch the view type + .o_cp_switch_buttons.show { + .dropdown-menu { + align-content: center; + display: flex; + flex-direction: row; + justify-content: space-around; + padding: 0; + + .btn { + border: { + bottom: 0; + radius: 0; + top: 0; + } + } + } + } + } +} + +// Normal views +.o_content, .modal-content { + max-width: 100%; + + // Form views + .o_form_view { + .o_form_sheet { + max-width: calc(100% - 32px); + } + + @include media-breakpoint-down(sm) { + min-width: auto; + + // Avoid overflow on forms with title and/or button box + .oe_button_box, + .oe_title, + { + max-width: 100%; + } + + // Avoid overflow on modals + .o_form_sheet { + min-width: auto; + } + + // Render website inputs properly in phones + .o_group .o_field_widget.o_text_overflow { + // Overrides another !important + width: auto !important; + } + + // Make all input groups vertical + .o_group_col_6 { + width: 100%; + } + + // Statusbar buttons dropdown for mobiles + .o_statusbar_buttons_dropdown { + border: { + bottom: 0; + radius: 0; + top: 0; + } + height: 100%; + } + .o_statusbar_buttons > .btn { + border-radius: 0; + border: 0; + width: 100%; + margin-bottom: 0.2rem; + + &:last-child { + margin-bottom: 0; + } + } + + .o_statusbar_status { + // Arrow from rightmost button exceeds allowed width + .o_arrow_button:first-child::before { + content: none; + display: none; + } + } + + // Full width in form sheets + .o_form_sheet, + .oe_chatter, + { + min-width: auto; + max-width: 98%; + } + + // Settings pages + .app_settings_block { + .row { + margin: 0; + } + } + } + } + + // Sided chatter, if user wants + .o_chatter_position_sided & { + @include media-breakpoint-up(md) { + .o_form_view:not(.o_form_nosheet) { + display: flex; + flex-flow: row nowrap; + height: 100%; + + .o_form_sheet_bg { + flex: 1 1 60%; + overflow: auto; + } + + .o_chatter { + border-left: 1px solid gray('400'); + flex: 1 1 40%; + max-width: 50%; + min-width: 30%; + overflow: auto; + } + } + } + } +} diff --git a/web_responsive/static/src/js/web_responsive.js b/web_responsive/static/src/js/web_responsive.js index 0736aaf9d..8bd94cc35 100644 --- a/web_responsive/static/src/js/web_responsive.js +++ b/web_responsive/static/src/js/web_responsive.js @@ -1,532 +1,409 @@ -/* Copyright 2016 LasLabs Inc. +/* Copyright 2018 Tecnativa - Jairo Llopis * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ -odoo.define('web_responsive', function(require) { +odoo.define('web_responsive', function (require) { 'use strict'; - var Menu = require('web.Menu'); - var rpc = require('web.rpc'); - var SearchView = require('web.SearchView'); - var core = require('web.core'); - var config = require('web.config'); - var session = require('web.session'); - var ViewManager = require('web.ViewManager'); - var RelationalFields = require('web.relational_fields'); + var AbstractWebClient = require("web.AbstractWebClient"); + var AppsMenu = require("web.AppsMenu"); + var config = require("web.config"); + var core = require("web.core"); var FormRenderer = require('web.FormRenderer'); - var Widget = require('web.Widget'); + var Menu = require("web.Menu"); + var RelationalFields = require('web.relational_fields'); - var qweb = core.qweb; - - Menu.include({ - - // Force all_outside to prevent app icons from going into more menu - reflow: function() { - this._super('all_outside'); - }, - - /* Overload to collapse unwanted visible submenus - * @param allow_open bool Switch to allow submenus to be opened - */ - open_menu: function(id, allowOpen) { - this._super(id); - if (allowOpen) { - return; - } - var $clicked_menu = this.$secondary_menus.find('a[data-menu=' + id + ']'); - $clicked_menu.parents('.oe_secondary_submenu').css('display', ''); + /** + * Reduce menu data to a searchable format understandable by fuzzy.js + * + * `AppsMenu.init()` gets `menuData` in a format similar to this (only + * relevant data is shown): + * + * ```js + * { + * [...], + * children: [ + * // This is a menu entry: + * { + * action: "ir.actions.client,94", // Or `false` + * children: [... similar to above "children" key], + * name: "Actions", + * parent_id: [146, "Settings/Technical/Actions"], // Or `false` + * }, + * ... + * ] + * } + * ``` + * + * This format is very hard to process to search matches, and it would + * slow down the search algorithm, so we reduce it with this method to be + * able to later implement a simpler search. + * + * @param {Object} memo + * Reference to current result object, passed on recursive calls. + * + * @param {Object} menu + * A menu entry, as described above. + * + * @returns {Object} + * Reduced object, without entries that have no action, and with a + * format like this: + * + * ```js + * { + * "Discuss": {Menu entry Object}, + * "Settings": {Menu entry Object}, + * "Settings/Technical/Actions/Actions": {Menu entry Object}, + * ... + * } + * ``` + */ + function findNames (memo, menu) { + if (menu.action) { + var key = menu.parent_id ? menu.parent_id[1] + "/" : ""; + memo[key + menu.name] = menu; } - - }); - - SearchView.include({ - - // Prevent focus of search field on mobile devices - toggle_visibility: function(is_visible) { - $('div.oe_searchview_input').last().one( - 'focus', $.proxy(this.preventMobileFocus, this)); - return this._super(is_visible); - }, - - // It prevents focusing of search el on mobile - preventMobileFocus: function(event) { - if (this.isMobile()) { - event.preventDefault(); - } - }, - - // For lack of Modernizr, TouchEvent will do - isMobile: function() { - try { - document.createEvent('TouchEvent'); - return true; - } catch (ex) { - return false; - } + if (menu.children.length) { + _.reduce(menu.children, findNames, memo); } - }); + return memo; + } - var AppDrawer = Widget.extend({ + AppsMenu.include({ + events: _.extend({ + "keydown .search-input input": "_searchResultsNavigate", + "click .o-menu-search-result": "_searchResultChosen", + "shown.bs.dropdown": "_searchFocus", + "hidden.bs.dropdown": "_searchReset", + }, AppsMenu.prototype.events), - /* Provides all features inside of the application drawer navigation. - - Attributes: - directionCodes (str): Canonical key name to direction mappings. - deleteCodes + /** + * Rescue some menu data stripped out in original method. + * + * @override */ - - LEFT: 'left', - RIGHT: 'right', - UP: 'up', - DOWN: 'down', - - // These keys are ignored when presented as single input - MODIFIERS: [ - 'Alt', - 'ArrowDown', - 'ArrowLeft', - 'ArrowRight', - 'ArrowUp', - 'Control', - 'Enter', - 'Escape', - 'Meta', - 'Shift', - 'Tab', - ], - - isOpen: false, - keyBuffer: '', - keyBufferTime: 500, - keyBufferTimeoutEvent: false, - dropdownHeightFactor: 0.90, - initialized: false, - searching: false, - - init: function() { + init: function (parent, menuData) { this._super.apply(this, arguments); - this.directionCodes = { - 'left': this.LEFT, - 'right': this.RIGHT, - 'up': this.UP, - 'pageup': this.UP, - 'down': this.DOWN, - 'pagedown': this.DOWN, - '+': this.RIGHT, - '-': this.LEFT - }; - this.$searchAction = $('.app-drawer-search-action'); - this.$searchAction.hide(); - this.$searchResultsContainer = $('#appDrawerSearchResults'); - this.$searchInput = $('#appDrawerSearchInput'); - this.initDrawer(); - this.handleWindowResize(); - var $clickZones = $('.odoo_webclient_container, ' + - 'a.oe_menu_leaf, ' + - 'a.oe_menu_toggler, ' + - 'a.oe_logo, ' + - 'i.oe_logo_edit' + // Keep base64 icon for main menus + for (var n in this._apps) { + this._apps[n].web_icon_data = + menuData.children[n].web_icon_data; + } + // Store menu data in a format searchable by fuzzy.js + this._searchableMenus = _.reduce( + menuData.children, + findNames, + {} ); - $clickZones.click($.proxy(this.handleClickZones, this)); - this.$searchResultsContainer.click($.proxy(this.searchMenus, this)); - this.$el.find('.drawer-search-open').click( - $.proxy(this.searchMenus, this) - ); - this.$el.find('.drawer-search-close').hide().click( - $.proxy(this.closeSearchMenus, this) - ); - this.filter_timeout = $.Deferred(); - core.bus.on('resize', this, this.handleWindowResize); - core.bus.on('keydown', this, this.handleKeyDown); - core.bus.on('keyup', this, this.redirectKeyPresses); - core.bus.on('keypress', this, this.redirectKeyPresses); + // Search only after timeout, for fast typers + this._search_def = $.Deferred(); }, - // Provides initialization handlers for Drawer - initDrawer: function() { - this.$el = $('.drawer'); - this.$el.drawer(); - this.$el.one('drawer.opened', $.proxy(this.onDrawerOpen, this)); - - // Setup the iScroll options. - // You should be able to pass these to ``.drawer``, but scroll freezes. - this.$el.on( - 'drawer.opened', - function setIScrollProbes(){ - var onIScroll = $.proxy( - function() { - this.iScroll.refresh(); - }, - this - ); - // Scroll probe aggressiveness level - // 2 == always executes the scroll event except during momentum and bounce. - this.iScroll.options.probeType = 2; - this.iScroll.on('scroll', onIScroll); - // Initialize Scrollbars manually - this.iScroll.options.scrollbars = true; - this.iScroll.options.fadeScrollbars = true; - this.iScroll._initIndicators(); - } - ); - this.initialized = true; - }, - - // Provides handlers to hide drawer when "unfocused" - handleClickZones: function() { - this.$el.drawer('close'); - $('.o_sub_menu_content') - .parent() - .collapse('hide'); - $('.navbar-collapse').collapse('hide'); - }, - - // Resizes bootstrap dropdowns for screen - handleWindowResize: function() { - $('.dropdown-scrollable').css( - 'max-height', $(window).height() * this.dropdownHeightFactor - ); - }, - - /* Provide keyboard shortcuts for app drawer nav. - * - * It is required to perform this functionality only on the ``keydown`` - * event in order to prevent duplication of the arrow events. - * - * @param e The ``keydown`` event triggered by ``core.bus``. + /** + * @override */ - handleKeyDown: function(e) { - if (!this.isOpen){ - return; - } - var directionCode = $.hotkeys.specialKeys[e.keyCode.toString()]; - if (Object.keys(this.directionCodes).indexOf(directionCode) !== -1) { - var $link = false; - if (this.searching) { - var $collection = this.$el.find('#appDrawerMenuSearch a'); - $link = this.findAdjacentLink( - this.$el.find('#appDrawerMenuSearch a:first, #appDrawerMenuSearch a.web-responsive-focus').last(), - this.directionCodes[directionCode], - $collection, - true - ); - } else { - $link = this.findAdjacentLink( - this.$el.find('#appDrawerApps a:first, #appDrawerApps a.web-responsive-focus').last(), - this.directionCodes[directionCode] - ); - } - this.selectLink($link); - } else if ($.hotkeys.specialKeys[e.keyCode.toString()] === 'esc') { - // We either back out of the search, or close the app drawer. - if (this.searching) { - this.closeSearchMenus(); - } else { - this.handleClickZones(); - } - } else { - this.redirectKeyPresses(e); - } + start: function () { + this.$search_container = this.$(".search-container"); + this.$search_input = this.$(".search-input input"); + this.$search_results = this.$(".search-results"); + return this._super.apply(this, arguments); }, - /* Provide centralized key event redirects for the App Drawer. + /** + * Get all info for a given menu. * - * This method is for all key events not related to arrow navigation. + * @param {String} key + * Full path to requested menu. * - * @param e The key event that was triggered by ``core.bus``. + * @returns {Object} + * Menu definition, plus extra needed keys. */ - redirectKeyPresses: function(e) { - if ( !this.isOpen ) { - // Drawer isn't open; Ignore. - return; - } - - // Trigger navigation to pseudo-focused link - // & fake a click (in case of anchor link). - if (e.key === 'Enter') { - var href = $('.web-responsive-focus').attr('href'); - if (!_.isUndefined(href)) { - window.location.href = href; - this.handleClickZones(); - } - return; - } - - // Ignore any other modifier keys. - if (this.MODIFIERS.indexOf(e.key) !== -1) { - return; - } - - // Event is already targeting the search input. - // Perform search, then stop processing. - if ( e.target === this.$searchInput[0] ) { - this.searchMenus(); - return; - } - - // Prevent default event, - // redirect it to the search input, - // and search. - e.preventDefault(); - this.$searchInput.trigger({ - type: e.type, - key: e.key, - keyCode: e.keyCode, - which: e.which, - }); - this.searchMenus(); - + _menuInfo: function (key) { + var original = this._searchableMenus[key]; + return _.extend({ + action_id: parseInt(original.action.split(',')[1], 10), + }, original); }, - /* Performs close actions - * @fires ``drawer.closed`` to the ``core.bus`` - * @listens ``drawer.opened`` and sends to onDrawerOpen + /** + * Autofocus on search field on big screens. */ - onDrawerClose: function() { - core.bus.trigger('drawer.closed'); - this.closeSearchMenus(); - this.$el.one('drawer.opened', $.proxy(this.onDrawerOpen, this)); - this.isOpen = false; - // Remove inline style inserted by drawer.js - this.$el.css("overflow", ""); - }, - - /* Finds app links and register event handlers - * @fires ``drawer.opened`` to the ``core.bus`` - * @listens ``drawer.closed`` and sends to :meth:``onDrawerClose`` - */ - onDrawerOpen: function() { - this.closeSearchMenus(); - this.$appLinks = $('.app-drawer-icon-app').parent(); - this.selectLink($(this.$appLinks[0])); - this.$el.one('drawer.closed', $.proxy(this.onDrawerClose, this)); - core.bus.trigger('drawer.opened'); - this.isOpen = true; - this.$searchInput.val(""); - }, - - // Selects a link visibly & deselects others. - selectLink: function($link) { - $('.web-responsive-focus').removeClass('web-responsive-focus'); - if ($link) { - $link.addClass('web-responsive-focus'); + _searchFocus: function () { + if (!config.device.isMobile) { + this.$search_input.focus(); } }, /** - * Search matching menus immediately + * Reset search input and results + */ + _searchReset: function () { + this.$search_container.removeClass("has-results"); + this.$search_results.empty(); + this.$search_input.val(""); + }, + + /** + * Schedule a search on current menu items. + */ + _searchMenusSchedule: function () { + this._search_def.reject(); + this._search_def = $.Deferred(); + setTimeout(this._search_def.resolve.bind(this._search_def), 50); + this._search_def.done(this._searchMenus.bind(this)); + }, + + /** + * Search among available menu items, and render that search. */ _searchMenus: function () { - rpc.query({ - model: 'ir.ui.menu', - method: 'search_read', - kwargs: { - fields: ['action', 'display_name', 'id'], - domain: [ - ['name', 'ilike', this.$searchInput.val()], - ['action', '!=', false], - ], - context: session.user_context, - }, - }).then(this.showFoundMenus.bind(this)); + var query = this.$search_input.val(); + if (query === "") { + this.$search_container.removeClass("has-results"); + this.$search_results.empty(); + return; + } + var results = fuzzy.filter( + query, + _.keys(this._searchableMenus), + { + pre: "", + post: "", + } + ); + this.$search_container.toggleClass( + "has-results", + Boolean(results.length) + ); + this.$search_results.html( + core.qweb.render( + "web_responsive.MenuSearchResults", + { + results: results, + widget: this, + } + ) + ); }, /** - * Queue the next menu search for the search input + * Use chooses a search result, so we navigate to that menu + * + * @param {jQuery.Event} event */ - searchMenus: function() { - // Stop current search, if any - this.filter_timeout.reject(); - this.filter_timeout = $.Deferred(); - // Schedule a new search - this.filter_timeout.done(this._searchMenus.bind(this)); - setTimeout( - this.filter_timeout.resolve.bind(this.filter_timeout), - 200 - ); - // Focus search input - this.$searchInput = $('#appDrawerSearchInput').focus(); + _searchResultChosen: function (event) { + event.preventDefault(); + var $result = $(event.currentTarget), + text = $result.text().trim(), + data = $result.data(), + suffix = ~text.indexOf("/") ? "/" : ""; + // Load the menu view + this.trigger_up("menu_clicked", { + action_id: data.actionId, + id: data.menuId, + previous_menu_id: data.parentId, + }); + // Find app that owns the chosen menu + var app = _.find(this._apps, function (_app) { + return text.indexOf(_app.name + suffix) === 0; + }); + // Update navbar menus + core.bus.trigger("change_menu_section", app.menuID); }, - /* Display the menus that are provided as input. + /** + * Navigate among search results + * + * @param {jQuery.Event} event */ - showFoundMenus: function(menus) { - this.searching = true; - this.$el.find('#appDrawerApps').hide(); - this.$searchAction.hide(); - this.$el.find('.drawer-search-close').show(); - this.$el.find('.drawer-search-open').hide(); - this.$searchResultsContainer - // Render the results - .html( - core.qweb.render( - 'AppDrawerMenuSearchResults', - {menus: menus} - ) - ) - // Get the parent container and show it. - .closest('#appDrawerMenuSearch') - .show() - // Find the input, set focus. - .find('.menu-search-query') - .focus() - ; - var $menuLinks = this.$searchResultsContainer.find('a'); - $menuLinks.click($.proxy(this.handleClickZones, this)); - this.selectLink($menuLinks.first()); - }, - - /* Close search menu and switch back to app menu. - */ - closeSearchMenus: function() { - this.searching = false; - this.$el.find('#appDrawerApps').show(); - this.$el.find('.drawer-search-close').hide(); - this.$el.find('.drawer-search-open').show(); - this.$searchResultsContainer.closest('#appDrawerMenuSearch').hide(); - this.$searchAction.show(); - }, - - /* Returns the link adjacent to $link in provided direction. - * It also handles edge cases in the following ways: - * * Moves to last link if LEFT on first - * * Moves to first link if PREV on last - * * Moves to first link of following row if RIGHT on last in row - * * Moves to last link of previous row if LEFT on first in row - * * Moves to top link in same column if DOWN on bottom row - * * Moves to bottom link in same column if UP on top row - * @param $link jQuery obj of App icon link - * @param direction str of direction to go (constants LEFT, UP, etc.) - * @param $objs jQuery obj representing the collection of links. Defaults - * to `this.$appLinks`. - * @param restrictHorizontal bool Set to true if the collection consists - * only of vertical elements. - * @return jQuery obj for adjacent link - */ - findAdjacentLink: function($link, direction, $objs, restrictHorizontal) { - - if (_.isUndefined($objs)) { - $objs = this.$appLinks; + _searchResultsNavigate: function (event) { + // Exit soon when not navigating results + if (this.$search_results.is(":empty")) { + // Just in case it is the 1st search + this._searchMenusSchedule(); + return; } - - var obj = []; - var $rows = restrictHorizontal ? $objs : this.getRowObjs($link, this.$appLinks); - - switch (direction) { - case this.LEFT: - obj = $objs[$objs.index($link) - 1]; - if (!obj) { - obj = $objs[$objs.length - 1]; - } - break; - case this.RIGHT: - obj = $objs[$objs.index($link) + 1]; - if (!obj) { - obj = $objs[0]; - } - break; - case this.UP: - obj = $rows[$rows.index($link) - 1]; - if (!obj) { - obj = $rows[$rows.length - 1]; - } - break; - case this.DOWN: - obj = $rows[$rows.index($link) + 1]; - if (!obj) { - obj = $rows[0]; - } - break; - } - - if (obj.length) { + // Find current results and active element (1st by default) + var all = this.$search_results.find(".o-menu-search-result"), + pre_focused = all.filter(".active") || $(all[0]), + offset = all.index(pre_focused), + key = event.key; + // Transform tab presses in arrow presses + if (key === "Tab") { event.preventDefault(); + key = event.shiftKey ? "ArrowUp" : "ArrowDown"; } + switch (key) { + // Pressing enter is the same as clicking on the active element + case "Enter": + pre_focused.click(); + break; + // Navigate up or down + case "ArrowUp": + offset--; + break; + case "ArrowDown": + offset++; + break; + // Other keys trigger a search + default: + this._searchMenusSchedule(); + return; + } + // Allow looping on results + if (offset < 0) { + offset = all.length + offset; + } else if (offset >= all.length) { + offset -= all.length; + } + // Switch active element + var new_focused = $(all[offset]); + pre_focused.removeClass("active"); + new_focused.addClass("active"); + this.$search_results.scrollTo(new_focused, { + offset: { + top: this.$search_results.height() * -0.5, + }, + }); + }, + }); - return $(obj); + Menu.include({ + events: _.extend({ + // Clicking a hamburger menu item should close the hamburger + "click .o_menu_sections [role=menuitem]": "_hideMobileSubmenus", + // Opening any dropdown in the navbar should hide the hamburger + "show.bs.dropdown .o_menu_systray, .o_menu_apps": + "_hideMobileSubmenus", + }, Menu.prototype.events), + start: function () { + this.$menu_toggle = this.$(".o-menu-toggle"); + return this._super.apply(this, arguments); }, - /* Returns els in the same row - * @param @obj jQuery object to get row for - * @param $grid jQuery objects representing grid - * @return $objs jQuery objects of row + /** + * Hide menus for current app if you're in mobile */ - getRowObjs: function($obj, $grid) { - // Filter by object which middle lies within left/right bounds - function filterWithin(left, right) { - return function() { - var $this = $(this), - thisMiddle = $this.offset().left + $this.width() / 2; - return thisMiddle >= left && thisMiddle <= right; - }; + _hideMobileSubmenus: function () { + if ( + this.$menu_toggle.is(":visible") && + this.$section_placeholder.is(":visible") + ) { + this.$section_placeholder.collapse("hide"); } - var left = $obj.offset().left, - right = left + $obj.outerWidth(); - return $grid.filter(filterWithin(left, right)); - } + }, - }); - - // Init a new AppDrawer when the web client is ready - core.bus.on('web_client_ready', null, function () { - new AppDrawer(); - }); - - // if we are in small screen change default view to kanban if exists - ViewManager.include({ - get_default_view: function() { - var default_view = this._super(); - if (config.device.size_class <= config.device.SIZES.XS && - default_view.type !== 'kanban' && - this.views.kanban) { - default_view.type = 'kanban'; + /** + * No menu brand in mobiles + * + * @override + */ + _updateMenuBrand: function () { + if (!config.device.isMobile) { + return this._super.apply(this, arguments); } - return default_view; }, }); - // FieldStatus (responsive fold) RelationalFields.FieldStatus.include({ - _renderQWebValues: function () { - return { - selections: this.status_information, // Needed to preserve order - has_folded: _.filter(this.status_information, {'selected': false}).length > 0, - clickable: !!this.attrs.clickable, - }; - }, - _render: function () { - // FIXME: Odoo framework creates view values & render qweb in the - // same method. This cause a "double render" process to use - // new custom values. + /** + * Fold all on mobiles. + * + * @override + */ + _setState: function () { this._super.apply(this, arguments); - this.$el.html(qweb.render("FieldStatus.content", this._renderQWebValues())); - } + if (config.device.isMobile) { + _.map(this.status_information, function (value) { + value.fold = true; + }); + } + }, }); // Responsive view "action" buttons FormRenderer.include({ - _renderHeaderButtons: function (node) { - var self = this; - var $buttons = this._super(node); - var $container = $(qweb.render('web_responsive.MenuStatusbarButtons')); - $container.find('.o_statusbar_buttons_base').append($buttons); + /** + * In mobiles, put all statusbar buttons in a dropdown. + * + * @override + */ + _renderHeaderButtons: function () { + var $buttons = this._super.apply(this, arguments); + if ( + !config.device.isMobile || + !$buttons.is(":has(>:not(.o_invisible_modifier))") + ) { + return $buttons; + } - var $dropdownMenu = $container.find('.dropdown-menu'); - _.each(node.children, function (child) { - if (child.tag === 'button') { - $dropdownMenu.append($('
  • ').append(self._renderHeaderButton(child))); - } - }); - - return $container; - } + // $buttons must be appended by JS because all events are bound + $buttons.addClass("dropdown-menu"); + var $dropdown = $(core.qweb.render( + 'web_responsive.MenuStatusbarButtons' + )); + $buttons.addClass("dropdown-menu").appendTo($dropdown); + return $dropdown; + }, }); + /** + * Use ALT+SHIFT instead of ALT as hotkey triggerer. + * + * HACK https://github.com/odoo/odoo/issues/30068 - See it to know why. + * + * Cannot patch in `KeyboardNavigationMixin` directly because it's a mixin, + * not a `Class`, and altering a mixin's `prototype` doesn't alter it where + * it has already been used. + * + * Instead, we provide an additional mixin to be used wherever you need to + * enable this behavior. + */ + var KeyboardNavigationShiftAltMixin = { - return { - 'AppDrawer': AppDrawer, + /** + * Alter the key event to require pressing Shift. + * + * This will produce a mocked event object where it will seem that + * `Alt` is not pressed if `Shift` is not pressed. + * + * The reason for this is that original upstream code, found in + * `KeyboardNavigationMixin` is very hardcoded against the `Alt` key, + * so it is more maintainable to mock its input than to rewrite it + * completely. + * + * @param {keyEvent} keyEvent + * Original event object + * + * @returns {keyEvent} + * Altered event object + */ + _shiftPressed: function (keyEvent) { + var alt = keyEvent.altKey || keyEvent.key === "Alt", + newEvent = _.extend({}, keyEvent), + shift = keyEvent.shiftKey || keyEvent.key === "Shift"; + // Mock event to make it seem like Alt is not pressed + if (alt && !shift) { + newEvent.altKey = false; + if (newEvent.key === "Alt") { + newEvent.key = "Shift"; + } + } + return newEvent; + }, + + _onKeyDown: function (keyDownEvent) { + return this._super(this._shiftPressed(keyDownEvent)); + }, + + _onKeyUp: function (keyUpEvent) { + return this._super(this._shiftPressed(keyUpEvent)); + }, }; + // Include the SHIFT+ALT mixin wherever + // `KeyboardNavigationMixin` is used upstream + AbstractWebClient.include(KeyboardNavigationShiftAltMixin); }); diff --git a/web_responsive/static/src/less/app_drawer.less b/web_responsive/static/src/less/app_drawer.less deleted file mode 100644 index 8d7342c46..000000000 --- a/web_responsive/static/src/less/app_drawer.less +++ /dev/null @@ -1,129 +0,0 @@ -/* Copyright 2016 LasLabs Inc. - * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ - -.app-drawer-nav { - border-color: @dropdown-border; - background-color: @dropdown-bg; - border: 1px solid @dropdown-fallback-border; // IE8 fallback - border: 1px solid @dropdown-border; - -webkit-border-radius: @border-radius-base; - -moz-border-radius: @border-radius-base; - border-radius: @border-radius-base; - .box-shadow(0 6px 12px rgba(0, 0, 0, .175)); - background-clip: padding-box; - z-index: 10000; - - .o_tooltip { - z-index: 1051; - } - - .oe_logo { - margin-top: -11px; - position: relative; - img { - height: @app-drawer-title-height; - } - .oe_logo_edit { - position: absolute; - bottom: 0px; - width: 100%; - padding: 4px; - display: none; - color: @odoo-list-footer-bg-color; - background: rgba(37,37,37,0.9); - } - &:hover .oe_logo_edit_admin { - display: block; - } - } - - .navbar-left { - width: 100%; - - li { - padding: 0; - } - } - - .app-drawer-title { - float: none; - font-weight: bold; // Bold titles for apps in the app-drawer - } - - .app-drawer-panel-title { - line-height: 16px; - > .drawer-toggle { - padding-top: 17px; - padding-bottom: 17px; - cursor: pointer; - } - } - - .app-drawer-icon-app { - height: 100%; - width: 100%; - max-width: @app-drawer-icon-size; - max-height: @app-drawer-icon-size; - object-fit: contain; - object-position: center; - } - - .panel-body { - padding-top: @app-drawer-title-height; - } - - #appDrawerAppPanelHead { - position: absolute; - height: @app-drawer-title-height; - width: 100%; - } - - .app-drawer-search-panel { - - .panel-body { - padding-top: @padding-base-vertical; - } - - } - -} - -.drawer-nav { - width: @app-drawer-width; -} - -.drawer--left .drawer-nav { - left: -@app-drawer-width; -} - -.drawer--left.drawer-open .drawer-hamburger { - left: @app-drawer-width; -} - -.drawer--right .drawer-nav { - right: -@app-drawer-width; -} - -.drawer-open .oe-right-toolbar { - display: none; -} - -.drawer-closed .oe-right-toolbar { - display: block; -} - -/* App Drawer Toggle */ - -.app-drawer-toggle { - background-color: transparent; -} - -.app-drawer-toggle.navbar-toggle { - margin-left: 1em; -} - -/* Icon Focusing */ - -.web-responsive-focus { - .tab-focus(); -} diff --git a/web_responsive/static/src/less/form_view.less b/web_responsive/static/src/less/form_view.less deleted file mode 100644 index f367c322a..000000000 --- a/web_responsive/static/src/less/form_view.less +++ /dev/null @@ -1,212 +0,0 @@ -/* Copyright 2016 Ponto Suprimentos Ltda. - Copyright 2018 Alexandre Díaz - * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ - -@sheet-margin: @sheet-padding; -@chatter-side-width: 30%; - -// Sided Chatter -@media (min-width: @screen-md) { - .o_chatter_position_sided { - .o_form_view:not(.o_form_nosheet) { - display: flex; - height: 100%; - - .o_form_sheet_bg { - border-right: 1px solid @table-border-color; - overflow: auto; - flex: 1 1 auto; - } - - .oe_chatter { - overflow: auto; - flex: 0 0 @chatter-side-width; - - .o_chatter_topbar { - height: auto; - flex-wrap: wrap; - - button:last-of-type { - flex: 1 0 auto; - text-align: left; - } - - .o_followers { - order: -10; - flex: 0 1 100%; - } - } - - &:empty { - display: none; - } - } - } - } -} - -// Normal Chatter -.o_chatter_position_normal { - .oe_chatter { - max-width: initial; - } -} - -// Sticky Header & Footer in List View -.o_view_manager_content { - >div { - >.table-responsive { - >.o_list_view { - thead { - position: sticky; - top: 0; - } - tfoot { - position: sticky; - bottom: 0; - } - } - } - } -} - -.o_form_view { - // Form must fill 100% width in any size - .o_form_sheet_bg { - - .o_form_sheet { - min-width: auto; - max-width: 100%; - margin: @sheet-margin; - } - - @media (max-width: @screen-sm-max) { - padding: 0; - - .o_form_sheet { - border: none; - } - } - - .o_form_statusbar { - position: sticky; - top: 0; - z-index: 1; - - .o-status-more > li > button { - border: 0; - } - - .o_statusbar_buttons_container { - .o_statusbar_buttons_dropdown { - height: 100%; - - >#dropdownMenuHeader { - height: 100%; - border-top: 0; - border-bottom: 0; - border-radius: 0; - } - >.dropdown-menu > li > button { - width: 100%; - border-radius: 0; - border: 0; - } - } - - .o_statusbar_buttons_base > .o_statusbar_buttons { - .o-flex-flow(row, wrap); - >.btn { - @o-statusbar-buttons-vmargin: 4px; - min-height: @odoo-statusbar-height - 2 * @o-statusbar-buttons-vmargin; - margin: @o-statusbar-buttons-vmargin 3px @o-statusbar-buttons-vmargin 0; - padding-top: 2px; - padding-bottom: 2px; - } - } - } - } - } - - // No overflowing buttons or titles - .oe_button_box, .oe_title { - max-width: 100%; - } - - @media (max-width: @screen-xs) { - .o_form_field > .o_form_input_dropdown { - width: 80%; - } - } - .o_group { - &.o_inner_group > tbody > tr > td { - .note-editor > .note-toolbar { - // prevent wysiwyg editor buttons from expanding the screen - white-space: initial; - } - } - // reduce form maximum columns for smaller screens - @media (max-width: @screen-xs-max) { - .o-generate-groups(12); - .o-generate-groups(@n, @i: 1) when (@i =< @n) { - .o_group_col_@{i} { - width: 100%; - } - .o-generate-groups(@n, @i + 1); - } - } - // break field label into a separate line from field on small screens - @media (max-width: @screen-xs) { - &.o_inner_group { - display: block; - > tbody { - display: block; - > tr { - margin-top: 8px; - .o-flex-display(); - .o-flex-flow(row, wrap); - > td { - .o-flex(1, 0, auto); - padding: 0; - display: block; - padding: 0; - // odoo adds a `style="width: 100%"` by javascript - // directly on the tag so we need `!important` here: - width: auto!important; - max-width: 100%; - &.o_td_label { - border-right: 0; - // keep 6% space on line to fit checkboxes - // see above about `!important` - width: 94%!important; - } - } - } - } - } - } - } - - // Make image editing controls always available, instead of depending on resolution or hover - .o_form_field_image > .o_form_image_controls { - position: initial; - opacity: 1; - > .fa { - width: 50%; - padding: 6px; - margin: 0px; - text-align: center; - } - > .fa.o_select_file_button { - background: @odoo-brand-primary; - } - > .fa.o_clear_file_button { - background: @brand-danger; - } - } - - // Adapt chatter widget to small viewports - .oe_chatter { - min-width: inherit; - } -} diff --git a/web_responsive/static/src/less/main.less b/web_responsive/static/src/less/main.less deleted file mode 100644 index 86806dc59..000000000 --- a/web_responsive/static/src/less/main.less +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright 2016 LasLabs Inc. - * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ - -body { - width: 100%; - height: 100%; - - // Do not fix the search part, it's too big for small screens - @media (max-width: @screen-sm-max) { - overflow: inherit; - .odoo { - .oe-view-manager { - overflow: inherit; - } - } - } -} - -main { - width: 100%; - height: 100%; - overflow: hidden; -} - -.navbar { - z-index: 10; -} - -.o_cp_switch_buttons { - .active { - z-index: 10; - } -} - -.o_sub_menu { - .o_sub_menu_logo { - display: none; - } - .o_sub_menu_footer { - display: none; - } -} - -.o_tooltip.active { - z-index: 1051; -} - -.o_web_client { - >.o_main { - overflow: auto; - > .o_main_content { - overflow: initial; - > .o_content { - @media (max-width: @screen-xs-max) { - overflow: initial; - } - - @media (min-width: @screen-sm-min) { - // .o_content is the one to display horizontal scrolling in - // case of wide tables - .table-responsive { - overflow-x: visible; - } - } - } - } - } -} - -@media (max-width: @screen-sm-max) { - .o_control_panel { - // Remove z-index from CP buttons so it doesn't overlap the menu - .btn-group > .btn.active { - z-index: initial; - } - - // Better horizontal space usage for buttons - justify-content: space-between; - .o_cp_left, .o_cp_right { - width: inherit; - } - - .o_search_options > .o_dropdown { - &.hidden-xs { - // No other way to display "Group By" button :( - display: inline-block !important; - } - - // Hack to hide text and display larger icons - > .btn { - font-size: 0; - > .fa { - font-size: @odoo-font-size-base * 1.4; - } - } - } - } -} - -.o_chat_window { - z-index: 1000; -} diff --git a/web_responsive/static/src/less/navbar.less b/web_responsive/static/src/less/navbar.less deleted file mode 100644 index 0f5a40203..000000000 --- a/web_responsive/static/src/less/navbar.less +++ /dev/null @@ -1,195 +0,0 @@ -/* Copyright 2016 LasLabs Inc. - * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ - -header { - margin: 0; - padding: 0; - - @media print { - display: none; - } - - > .main-nav { - display: block; - white-space: nowrap; - - .navbar-systray { - white-space: nowrap; - @media (max-width: @screen-xs-max) { - position: absolute; - top: 0; - right: 56px; - } - > .oe_user_menu_placeholder > li > a { - > .oe_topbar_avatar { - border-radius: 50%; - margin-top: -8px; - max-height: 36px; - height: 36px; - width: 36px; - } - .oe_topbar_name { - position: relative; - top: -3px; - @media (max-width: @screen-xs-max) { - display: none; - } - } - .caret { - position: relative; - top: -3.5px; - } - } - .o_switch_company_menu { - .oe_topbar_name { - @media (max-width: @screen-xs-max) { - display: none; - } - } - } - > .oe_systray > li > a { - .fa { - position: relative; - top: 3px; - font-size: 16px; - } - .caret { - position: relative; - top: 0.5px; - } - } - } - - .navbar-right { - float: right; - > li { - float: left; - } - @media (max-width: @screen-xs-max) { - .navbar-nav .open .dropdown-menu { - position: fixed; - top: 46px; - bottom: 0; - left: 0; - right: 0; - overflow: auto; - float: left; - background-color: @odoo-view-background-color; - border: 1px solid rgba(0, 0, 0, 0.15); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - } - } - } - - .container-fluid:before, .container-fluid:after, .navbar-collapse:before, .navbar-collapse:after { - display: inline; - } - - > .container-fluid { - margin: 0; - padding: 0; - - @media (max-width: @screen-xs-max) { - > .navbar-collapse { - margin: 0; - padding: 0; - overflow: auto; - &.collapsing { - overflow: hidden; - } - } - } - > .navbar-header { - margin: 0; - padding: 0; - > .drawer-toggle, .navbar-toggle { - margin: 0; - padding: 0; - border: 0px; - border-radius: 0px; - > i.fa, div.fa { - padding: 17px 14px 16px; - } - } - } - - .o_sub_menu > .o_sub_menu_content > .oe_secondary_menu { - ul.dropdown-menu > li.dropdown-header { - color: @odoo-view-background-color; - text-decoration: none; - background-color: @odoo-main-color-muted; - font-weight: bold; - } - @media (min-width: @screen-sm-min) { - height: @navbar-height; - } - margin: 0; - padding: 0; - > li { - @media (min-width: @screen-sm-min) { - height: @navbar-height; - } - margin: 0; - padding: 0; - &.app-name { - display: block; - padding: 7px 8px; - > .oe_menu_text { - font-size: 20px; - } - @media (min-width: @screen-sm-min) { - padding: 8.5px 12px; - } - } - > a { - margin: 0; - @media (min-width: @screen-sm-min) { - height: @navbar-height; - padding: 14px 8px; - } - } - } - } - } - - > .navbar-right.o_menu_systray { - display: inline; - margin: 0; - padding: 0; - > ul { - margin: 0; - padding: 0; - > li > a { - margin: 0; - padding: 13px 8px; - height: @navbar-height; - } - } - } - - .badge { - position: absolute; - top: 3px; - right: @navbar-padding-horizontal / 2; - } - - ul.nav > li > a { - padding: @app-drawer-navbar-padding-vertical @app-drawer-padding-horizontal; - } - - .o_planner_systray > .progress { - margin-top: 15px; - } - } -} - -a.navbar-collapse.collapse { - @media (min-width: @screen-sm-min) { - padding-bottom: @app-drawer-navbar-padding-vertical; - padding-top: @app-drawer-navbar-padding-vertical; - } -} - -.dropdown-scrollable { - overflow-x: hidden; -} diff --git a/web_responsive/static/src/less/variables.less b/web_responsive/static/src/less/variables.less deleted file mode 100644 index 34eda0d48..000000000 --- a/web_responsive/static/src/less/variables.less +++ /dev/null @@ -1,18 +0,0 @@ -/* Copyright 2016 LasLabs Inc. - * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ - -// App Drawer / Icons -@app-drawer-icon-size: 6em; -@app-drawer-icon-margin: 1em; -@app-drawer-width: 80%; -@app-drawer-title-height: 54px; - -// Navbar -@navbar-height: 46px; -@app-drawer-navbar-height: @navbar-height / 2; -@app-drawer-navbar-padding-vertical: @navbar-padding-vertical / 2; -@app-drawer-padding-horizontal: @navbar-padding-horizontal / 2; - -// Drawer Toggle -@drawer-toggle-height: @navbar-height; -@drawer-toggle-width: @navbar-height; diff --git a/web_responsive/static/src/xml/app_drawer_menu_search.xml b/web_responsive/static/src/xml/app_drawer_menu_search.xml deleted file mode 100644 index 027ff9d93..000000000 --- a/web_responsive/static/src/xml/app_drawer_menu_search.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - -
  • - -

    - -

    -
    -
  • - - diff --git a/web_responsive/static/src/xml/apps.xml b/web_responsive/static/src/xml/apps.xml new file mode 100644 index 000000000..b5a14bb51 --- /dev/null +++ b/web_responsive/static/src/xml/apps.xml @@ -0,0 +1,58 @@ + + + + diff --git a/web_responsive/static/src/xml/form_view.xml b/web_responsive/static/src/xml/form_view.xml index 51537b999..4a0ec6f59 100644 --- a/web_responsive/static/src/xml/form_view.xml +++ b/web_responsive/static/src/xml/form_view.xml @@ -2,63 +2,140 @@ - - - e - - - - - - - - - - - - - -
      - -
    • - -
    • -
    - -
    -
    - - - - - - - - + + + + -
    -
    - -
    -
    - -
      -
    -
    +
    + +
    + + + + e + + + + + d + + + + + + + Edit + + + + + + + Create + + + + + + + Save + + + + + + + Discard + + + + + + + + + + + + + + + + + + d + + + + + + + Create + + + + + + + Save + + + + + + + Discard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web_responsive/static/src/xml/navbar.xml b/web_responsive/static/src/xml/navbar.xml index f3c9934d6..b9acd58ef 100644 --- a/web_responsive/static/src/xml/navbar.xml +++ b/web_responsive/static/src/xml/navbar.xml @@ -1,11 +1,16 @@ - diff --git a/web_responsive/static/tests/js/web_responsive.js b/web_responsive/static/tests/js/web_responsive.js deleted file mode 100644 index 5a3a43b0c..000000000 --- a/web_responsive/static/tests/js/web_responsive.js +++ /dev/null @@ -1,290 +0,0 @@ -/* global QUnit */ -/* Copyright 2016 LasLabs Inc. - * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ - -odoo.define('web_responsive.test', function(require) { - "use strict"; - - var core = require('web.core'); - var responsive = require('web_responsive'); - - QUnit.module('web_responsive', { - beforeEach: function() { - var $el = $( - '
    ' + - '
    ' + - '' + - '
    ' + - '
      ' + - '
    • ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' - ); - - this.$clickZone = $el.find('a.oe_menu_leaf'); - this.$secondaryMenu = $el.find('div.o_sub_menu_content').parent(); - this.$dropdown = $el.find('div.dropdown-scrollable'); - - this.document = $("#qunit-fixture"); - this.document.append($el); - - this.drawer = new responsive.AppDrawer(); - }, - - linkGrid: function() { - for(var i = 0; i < 3; i++){ - this.drawer.$el.append( - $('
    ').append( - $(' - - - - - -