mirror of https://github.com/OCA/web.git
[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!pull/1819/head
parent
dbca634dcc
commit
46ac45a5f1
|
@ -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 <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
|
||||
<https://github.com/OCA/web/pull/446#issuecomment-254827880>`_
|
||||
* 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 <https://github.com/OCA/web/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 <https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2011.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
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 <https://github.com/OCA/web/tree/11.0/web_responsive>`_ project on GitHub.
|
||||
This module is part of the `OCA/web <https://github.com/OCA/web/tree/12.0/web_responsive>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
|
|
|
@ -1,4 +1 @@
|
|||
# Copyright 2018 Alexandre Díaz
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from . import models
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
|
|
|
@ -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 <pedro.baeza@gmail.com>\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 <yajo.sk8@gmail.com>\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 "<span class=\"sr-only\">Toggle App Drawer</span>"
|
||||
msgstr ""
|
||||
"<span class=\"sr-only\">Mostrar/ocultar selector de aplicaciones</span>"
|
||||
|
||||
#. module: web_responsive
|
||||
#: model:ir.ui.view,arch_db:web_responsive.webclient_bootstrap
|
||||
msgid "<span class=\"sr-only\">Toggle Navigation</span>"
|
||||
msgstr "<span class=\"sr-only\">Mostrar/Ocultar navegación</span>"
|
||||
|
||||
#. 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 <b class=\"caret\"/>"
|
||||
msgstr "Más <b class=\"caret\"/>"
|
||||
#. 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"
|
||||
|
|
|
@ -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 "<i>Searching:</i>"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_responsive
|
||||
#: model:ir.ui.view,arch_db:web_responsive.menu
|
||||
msgid "<span class=\"hidden-xs\">\n"
|
||||
" &nbsp;|&nbsp;\n"
|
||||
" </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_responsive
|
||||
#: model:ir.ui.view,arch_db:web_responsive.webclient_bootstrap
|
||||
msgid "<span class=\"sr-only\">Toggle App Drawer</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_responsive
|
||||
#: model:ir.ui.view,arch_db:web_responsive.webclient_bootstrap
|
||||
msgid "<span class=\"sr-only\">Toggle Navigation</span>"
|
||||
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 <b class=\"caret\"/>"
|
||||
#. 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
<https://github.com/OCA/web/pull/446#issuecomment-254827880>`_
|
||||
* 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?
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
|
|
|
@ -367,15 +367,54 @@ ul.auto-toc {
|
|||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/11.0/web_responsive"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-11-0/web-11-0-web_responsive"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/11.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
|
||||
<p>This module provides a mobile compliant interface for Odoo Community web.</p>
|
||||
<p>Features:</p>
|
||||
<ul class="simple">
|
||||
<li>New navigation with an App drawer</li>
|
||||
<li>Keyboard shortcuts for easier navigation</li>
|
||||
<li>Display kanban views for small screens if an action or field One2x</li>
|
||||
<li>Set chatter side (Optional per user)</li>
|
||||
<li>Quick search</li>
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/12.0/web_responsive"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-12-0/web-12-0-web_responsive"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
|
||||
<p>This module adds responsiveness to web backend.</p>
|
||||
<p>Features for all devices:</p>
|
||||
<ul>
|
||||
<li><p class="first">New navigation with an app drawer</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/48417193-09a1e080-e74a-11e8-8a0c-e73eb689b2fb.gif" src="https://user-images.githubusercontent.com/973709/48417193-09a1e080-e74a-11e8-8a0c-e73eb689b2fb.gif" />
|
||||
</li>
|
||||
<li><p class="first">Quick menu search from the app drawer</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif" src="https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif" />
|
||||
</li>
|
||||
</ul>
|
||||
<p>Features for mobile:</p>
|
||||
<ul>
|
||||
<li><p class="first">App-specific submenus are shown on full screen when toggling them from the
|
||||
“hamburger” menu</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/48417297-51286c80-e74a-11e8-9a47-22c810b18c43.gif" src="https://user-images.githubusercontent.com/973709/48417297-51286c80-e74a-11e8-9a47-22c810b18c43.gif" />
|
||||
</li>
|
||||
<li><p class="first">View type picker dropdown displays confortably</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/50964322-e3d55580-14c6-11e9-8249-48db9539600f.gif" src="https://user-images.githubusercontent.com/973709/50964322-e3d55580-14c6-11e9-8249-48db9539600f.gif" />
|
||||
</li>
|
||||
<li><p class="first">Top app bar is always visible, but the control panel is hidden when
|
||||
scrolling down, to save some vaulable vertical space</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/50964496-5cd4ad00-14c7-11e9-9261-fd223a329d02.gif" src="https://user-images.githubusercontent.com/973709/50964496-5cd4ad00-14c7-11e9-9261-fd223a329d02.gif" />
|
||||
</li>
|
||||
<li><p class="first">Form status bar action and status buttons are collapsed in dropdowns.
|
||||
Other control panel buttons use icons to save space.</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/50965446-e08f9900-14c9-11e9-92d6-dda472cb6557.gif" src="https://user-images.githubusercontent.com/973709/50965446-e08f9900-14c9-11e9-92d6-dda472cb6557.gif" />
|
||||
</li>
|
||||
<li><p class="first">Breadcrumbs navigation is collapsed with a “back arrow” button.</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/50965168-1d0ec500-14c9-11e9-82a0-dfee82ed0861.gif" src="https://user-images.githubusercontent.com/973709/50965168-1d0ec500-14c9-11e9-82a0-dfee82ed0861.gif" />
|
||||
</li>
|
||||
</ul>
|
||||
<p>Features for computers:</p>
|
||||
<ul>
|
||||
<li><p class="first">Keyboard shortcuts for easier navigation, <strong>using ``Alt + Shift + [key]``</strong>
|
||||
combination instead of just <tt class="docutils literal">Alt + [key]</tt>.
|
||||
See <a class="reference external" href="https://github.com/odoo/odoo/issues/30068">https://github.com/odoo/odoo/issues/30068</a> to understand why.</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/48417578-ff341680-e74a-11e8-8881-017709e912bc.png" src="https://user-images.githubusercontent.com/973709/48417578-ff341680-e74a-11e8-8881-017709e912bc.png" />
|
||||
</li>
|
||||
<li><p class="first">Autofocus on search menu box when opening the drawer</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif" src="https://user-images.githubusercontent.com/973709/48417213-17576600-e74a-11e8-846a-57691e82636b.gif" />
|
||||
</li>
|
||||
<li><p class="first">Set chatter on the side of the screen, optional per user</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/48417270-41108d00-e74a-11e8-9172-cba825d027ed.gif" src="https://user-images.githubusercontent.com/973709/48417270-41108d00-e74a-11e8-9172-cba825d027ed.gif" />
|
||||
</li>
|
||||
<li><p class="first">Full width form sheets</p>
|
||||
<img alt="https://user-images.githubusercontent.com/973709/48417428-ac5a5f00-e74a-11e8-8839-5bc538c54c1d.png" src="https://user-images.githubusercontent.com/973709/48417428-ac5a5f00-e74a-11e8-8839-5bc538c54c1d.png" />
|
||||
</li>
|
||||
</ul>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
|
@ -395,35 +434,21 @@ ul.auto-toc {
|
|||
<h1><a class="toc-backref" href="#id1">Usage</a></h1>
|
||||
<p>The following keyboard shortcuts are implemented:</p>
|
||||
<ul class="simple">
|
||||
<li>Toggle App Drawer - <cite>ActionKey <https://en.wikipedia.org/wiki/Access_key#Access_in_different_browsers></cite> + <tt class="docutils literal">A</tt></li>
|
||||
<li>Navigate Apps Drawer - Arrow Keys</li>
|
||||
<li>Type to select App Links</li>
|
||||
<li><tt class="docutils literal">esc</tt> to close App Drawer</li>
|
||||
<li>Toggle app drawer - <tt class="docutils literal">Alt + Shift + H</tt></li>
|
||||
<li>Navigate app search results - Arrow keys</li>
|
||||
<li>Choose app result - <tt class="docutils literal">Enter</tt></li>
|
||||
<li><tt class="docutils literal">Esc</tt> to close app drawer</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="known-issues-roadmap">
|
||||
<h1><a class="toc-backref" href="#id2">Known issues / Roadmap</a></h1>
|
||||
<p>Note: Data added to the footer <tt class="docutils literal">support_branding</tt> is not shown while using
|
||||
this module.</p>
|
||||
<ul class="simple">
|
||||
<li>Provide full menu search feature instead of just App search</li>
|
||||
<li>Drag drawer from left to open in mobile</li>
|
||||
<li>Figure out how to test focus on hidden elements for keyboard nav tests</li>
|
||||
<li>If you resize the window, body gets a wrong <tt class="docutils literal">overflow: auto</tt> css property
|
||||
and you need to refresh your view or open/close the app drawer to fix that.</li>
|
||||
<li>Override LESS styling to allow for responsive widget layouts</li>
|
||||
<li>Adding <tt class="docutils literal">oe_main_menu_navbar</tt> ID to the top navigation bar triggers some
|
||||
great styles, but also <a class="reference external" href="https://github.com/OCA/web/pull/446#issuecomment-254827880">JavaScript that causes issues on mobile</a></li>
|
||||
<li>Sticky header and footer in list view only works on certain browsers:
|
||||
<a class="reference external" href="https://caniuse.com/#search=sticky">https://caniuse.com/#search=sticky</a> (note that the used feature is in
|
||||
<cite>thead</cite>).</li>
|
||||
<li>On Android (FireFox) - clicking the search icon does not
|
||||
focus the search input.</li>
|
||||
<li>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.</li>
|
||||
<li>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.</li>
|
||||
<li>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.</li>
|
||||
<li>App navigation with keyboard.</li>
|
||||
<li>Make it more beautiful. Maybe OCA-branded?</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
|
@ -431,7 +456,7 @@ users to filter on complete paths of menus and not only on the last item.</li>
|
|||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
|
||||
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
|
||||
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2011.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
|
@ -461,7 +486,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
|
|||
<p>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.</p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/11.0/web_responsive">OCA/web</a> project on GitHub.</p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/12.0/web_responsive">OCA/web</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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 <design@blivesta.com> (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;
|
||||
}
|
||||
}
|
|
@ -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 <john@mclear.co.uk>
|
||||
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 <body> elements the text attribute represents the obsolete text color, not the textContent.
|
||||
// we document that these routines do not work for <body> 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<br/>');
|
||||
}
|
||||
};
|
||||
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 <br>, 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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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 <design@blivesta.com> (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 = $('<div>').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);
|
||||
}
|
||||
};
|
||||
|
||||
}));
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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: "<b>",
|
||||
post: "</b>",
|
||||
}
|
||||
);
|
||||
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($('<LI>').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);
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2017 LasLabs Inc.
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="AppDrawerMenuSearchResults">
|
||||
<li class="menu-search-element" t-foreach="menus" t-as="menu">
|
||||
<a t-att-id="menu.id"
|
||||
t-attf-href="#action={{ menu.action and menu.action.split(',')[1] or ''}}&menu_id={{ menu.id }}">
|
||||
<h2 class="text-center">
|
||||
<t t-esc="menu.display_name" />
|
||||
</h2>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
</templates>
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2018 Tecnativa - Jairo Llopis
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<template>
|
||||
<t t-extend="AppsMenu">
|
||||
<!-- App icons should be more than a text -->
|
||||
<t t-jquery=".o_app > t" t-operation="replace">
|
||||
<t t-call="web_responsive.AppIcon"/>
|
||||
</t>
|
||||
|
||||
<!-- Same hotkey as in EE -->
|
||||
<t t-jquery=".full" t-operation="attributes">
|
||||
<attribute name="accesskey">a</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Search bar -->
|
||||
<t t-jquery="[t-as=app]" t-operation="before">
|
||||
<div class="search-container form-row align-items-center mb-4 col-12">
|
||||
<div class="search-input col-md-10 ml-auto mr-auto mb-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<i class="fa fa-search"/>
|
||||
</div>
|
||||
</div>
|
||||
<input type="search"
|
||||
placeholder="Search menus..."
|
||||
class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-results col-md-10 ml-auto mr-auto"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<!-- Separate app icon template, for easier inheritance -->
|
||||
<t t-name="web_responsive.AppIcon">
|
||||
<img class="o-app-icon"
|
||||
t-attf-src="data:image/png;base64,#{app.web_icon_data}"/>
|
||||
<span class="o-app-name">
|
||||
<t t-esc="app.name"/>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<!-- A search result -->
|
||||
<t t-name="web_responsive.MenuSearchResults">
|
||||
<t t-foreach="results" t-as="result">
|
||||
<t t-set="menu" t-value="widget._menuInfo(result.original)"/>
|
||||
<div t-attf-class="o-menu-search-result dropdown-item col-12 ml-auto mr-auto #{result_first ? 'active' : ''}"
|
||||
t-attf-style="background-image:url('data:image/png;base64,#{menu.web_icon_data}')"
|
||||
t-att-data-menu-id="menu.id"
|
||||
t-att-data-action-id="menu.action_id"
|
||||
t-att-data-parent-id="menu.parent_id[0]"
|
||||
t-raw="result.string"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
|
@ -2,63 +2,140 @@
|
|||
<!--
|
||||
Copyright 2017 LasLabs Inc.
|
||||
Copyright 2018 Alexandre Díaz
|
||||
Copyright 2018 Tecnativa - Jairo Llopis
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
-->
|
||||
|
||||
<templates id="form_view" xml:space="preserve">
|
||||
|
||||
<t t-extend="FormView.buttons">
|
||||
<t t-jquery="button[accesskey='a']" t-operation="attributes">
|
||||
<attribute name="accesskey">e</attribute>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="FieldStatus.content.button">
|
||||
<t t-jquery="button" t-operation="replace">
|
||||
<button type="button" t-att-data-value="i.id" t-att-disabled="disabled ? 'disabled' : undefined"
|
||||
t-attf-class="btn btn-sm o_arrow_button btn-#{i.selected ? 'primary' : 'default'}#{disabled ? ' disabled' : ''} #{button_css or ''}">
|
||||
<t t-esc="i.display_name"/>
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-extend="FieldStatus.content">
|
||||
<t t-jquery="[t-if='selection_folded.length']" t-operation="replace">
|
||||
<t t-if="selections && has_folded">
|
||||
<ul class="dropdown-menu o-status-more hidden-lg hidden-md" role="menu">
|
||||
<t t-set="button_css" t-value="'hidden-lg hidden-md'" />
|
||||
<li t-foreach="selections" t-as="i">
|
||||
<t t-if="!i.selected" t-call="FieldStatus.content.button"/>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn-sm o_arrow_button btn-default dropdown-toggle hidden-lg hidden-md" data-toggle="dropdown" aria-expanded="false">More <span class="caret"/></button>
|
||||
</t>
|
||||
</t>
|
||||
<t t-jquery="[t-foreach='selection_unfolded.reverse()']" t-operation="replace">
|
||||
<t t-if="selections">
|
||||
<t t-foreach="selections.reverse()" t-as="i">
|
||||
<t t-set="button_css" t-value="i.selected?'':'hidden-xs hidden-sm'" />
|
||||
<t t-call="FieldStatus.content.button"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<!-- Template for buttons that display only the icon in xs -->
|
||||
<t t-name="web_responsive.icon_button">
|
||||
<i t-attf-class="fa fa-#{icon}"
|
||||
t-att-title="label"/>
|
||||
<span class="d-none d-sm-inline" t-esc="label"/>
|
||||
</t>
|
||||
|
||||
<t t-name="web_responsive.MenuStatusbarButtons">
|
||||
<div class="o_statusbar_buttons_container">
|
||||
<div class="o_statusbar_buttons_base hidden-xs hidden-sm">
|
||||
<!-- Normal Buttons Zone -->
|
||||
</div>
|
||||
<div class="dropdown o_statusbar_buttons_dropdown hidden-lg hidden-md">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuHeader" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
Task
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuHeader">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="o_statusbar_buttons_dropdown btn btn-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'cogs'"/>
|
||||
<t t-set="label">Quick actions</t>
|
||||
</t>
|
||||
</button>
|
||||
<!-- A div.o_statusbar_buttons.dropdown-menu
|
||||
is appended here from JS -->
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-extend="FormView.buttons">
|
||||
<!-- Change "Edit" button hotkey to "E" -->
|
||||
<t t-jquery=".o_form_button_edit" t-operation="attributes">
|
||||
<attribute name="accesskey">e</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Change "Discard" button hotkey to "D" -->
|
||||
<t t-jquery=".o_form_button_cancel" t-operation="attributes">
|
||||
<attribute name="accesskey">d</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery=".o_form_button_edit" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'pencil'"/>
|
||||
<t t-set="label">Edit</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_form_button_create" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'"/>
|
||||
<t t-set="label">Create</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_form_button_save" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'check'"/>
|
||||
<t t-set="label">Save</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_form_button_cancel" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'times'"/>
|
||||
<t t-set="label">Discard</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="KanbanView.buttons">
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery="button" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'"/>
|
||||
<t t-set="label" t-value="create_text || _t('Create')"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="ListView.buttons">
|
||||
<!-- Change "Discard" button hotkey to "D" -->
|
||||
<t t-jquery=".o_list_button_discard" t-operation="attributes">
|
||||
<attribute name="accesskey">d</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery=".o_list_button_add" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'"/>
|
||||
<t t-set="label">Create</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_list_button_save" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'check'"/>
|
||||
<t t-set="label">Save</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_list_button_discard" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'times'"/>
|
||||
<t t-set="label">Discard</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="Sidebar">
|
||||
<!-- Replace some common sections by icons in mobile -->
|
||||
<t t-jquery=".o_dropdown_toggler_btn t[t-esc='section.label']"
|
||||
t-operation="replace">
|
||||
<t t-set="label" t-value="section.label"/>
|
||||
<t t-if="section.name == 'files'">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'paperclip'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="section.name == 'print'">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'print'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="section.name == 'other'">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'wrench'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span t-esc="label"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2017 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||
<!-- Copyright 2017-2018 Tecnativa - Jairo Llopis
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<template>
|
||||
<t t-extend="SwitchCompanyMenu">
|
||||
<t t-jquery=".oe_topbar_name" t-operation="before">
|
||||
<i class="fa fa-building visible-xs-inline-block"/>
|
||||
<t t-extend="Menu">
|
||||
<t t-jquery=".o_menu_apps" t-operation="after">
|
||||
<!-- Hamburger button to show submenus in sm screens -->
|
||||
<button class="o-menu-toggle d-md-none"
|
||||
data-toggle="collapse"
|
||||
data-target=".o_main_navbar .o_menu_sections">
|
||||
<i class="fa fa-bars"/>
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
|
|
@ -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 = $(
|
||||
'<div class="drawer drawer--left">' +
|
||||
'<header role="banner">' +
|
||||
'<button class="drawer-toggle">' +
|
||||
'<span class="drawer-hamburger-icon"/>' +
|
||||
'</button>' +
|
||||
'<nav class="drawer-nav">' +
|
||||
'<ul class="drawer-menu">' +
|
||||
'<li class="drawer-menu-item"/>' +
|
||||
'</ul>' +
|
||||
'</nav>' +
|
||||
'<div class="panel-title" id="appDrawerAppPanelHead"></div>' +
|
||||
'</header>' +
|
||||
'<main role="main"></main>' +
|
||||
'<a class="oe_menu_leaf"/>' +
|
||||
'<div>' +
|
||||
'<div class="o_sub_menu_content"></div>' +
|
||||
'</div>' +
|
||||
'<div class="dropdown-scrollable"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
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(
|
||||
$('<div class="row">').append(
|
||||
$('<a class="col-md-6" id="a_' + i + '"><span class="app-drawer-icon-app /></a>' +
|
||||
'<a class="col-md-6" id="b_' + i + '"><span class="app-drawer-icon-app /></a>'
|
||||
)
|
||||
)
|
||||
);
|
||||
this.drawer.$appLinks = this.drawer.$el.find('a.col-md-6');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('It should set initialized after success init',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
assert.ok(this.drawer.initialized);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should close drawer after click on clickZone',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.$clickZone.click();
|
||||
|
||||
var self = this;
|
||||
var d = $.Deferred();
|
||||
setTimeout(function() {
|
||||
assert.ok(self.drawer.$el.hasClass('drawer-close'));
|
||||
d.resolve();
|
||||
}, 100);
|
||||
|
||||
return d;
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should collapse open secondary menus during handleClickZones',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.$clickZone.click();
|
||||
|
||||
var self = this;
|
||||
var d = $.Deferred();
|
||||
setTimeout(function() {
|
||||
assert.equal(self.$secondaryMenu.attr('aria-expanded'), 'false');
|
||||
d.resolve();
|
||||
}, 200);
|
||||
|
||||
return d;
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should update max-height on scrollable dropdowns',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.drawer.handleWindowResize();
|
||||
|
||||
var height = $(window).height() * this.drawer.dropdownHeightFactor;
|
||||
var actual = parseFloat(this.$dropdown.css('max-height'));
|
||||
|
||||
var pass = Math.abs(actual - height) < 0.001;
|
||||
|
||||
assert.pushResult({
|
||||
result: pass,
|
||||
actual: actual,
|
||||
expect: height,
|
||||
message: ''
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should trigger core bus event for drawer close',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.drawer.onDrawerOpen();
|
||||
var d = $.Deferred();
|
||||
core.bus.on('drawer.closed', this, function() {
|
||||
assert.ok(true);
|
||||
d.resolve();
|
||||
});
|
||||
|
||||
this.drawer.$el.trigger({type: 'drawer.closed'});
|
||||
return d;
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should set isOpen to false when closing',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.drawer.onDrawerOpen();
|
||||
|
||||
var self = this;
|
||||
var d = $.Deferred();
|
||||
setTimeout(function() {
|
||||
assert.equal(self.drawer.isOpen, false);
|
||||
d.resolve();
|
||||
}, 100);
|
||||
|
||||
this.drawer.$el.trigger({type: 'drawer.closed'});
|
||||
|
||||
return d;
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should set isOpen to true when opening',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.drawer.$el.trigger({type: 'drawer.opened'});
|
||||
|
||||
var self = this;
|
||||
var d = $.Deferred();
|
||||
setTimeout(function() {
|
||||
assert.ok(self.drawer.isOpen);
|
||||
d.resolve();
|
||||
}, 100);
|
||||
|
||||
return d;
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should trigger core bus event for drawer open',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.drawer.onDrawerOpen();
|
||||
var d = $.Deferred();
|
||||
|
||||
core.bus.on('drawer.opened', this, function() {
|
||||
assert.ok(true);
|
||||
d.resolve();
|
||||
});
|
||||
|
||||
this.drawer.$el.trigger({type: 'drawer.opened'});
|
||||
return d;
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should choose link to right',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.linkGrid();
|
||||
|
||||
var $appLink = $('#a_1'),
|
||||
$expect = $('#a_2'),
|
||||
$res = this.drawer.findAdjacentLink(
|
||||
$appLink, this.drawer.RIGHT
|
||||
);
|
||||
|
||||
assert.equal($res[0].id, $expect[0].id);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should choose link to left',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.linkGrid();
|
||||
var $appLink = $('#a_2'),
|
||||
$expect = $('#a_1'),
|
||||
$res = this.drawer.findAdjacentLink(
|
||||
$appLink, this.drawer.LEFT
|
||||
);
|
||||
assert.equal($res[0].id, $expect[0].id);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should choose link above',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.linkGrid();
|
||||
var $appLink = $('#a_1'),
|
||||
$expect = $('#a_0'),
|
||||
$res = this.drawer.findAdjacentLink(
|
||||
$appLink, this.drawer.UP
|
||||
);
|
||||
assert.equal($res[0].id, $expect[0].id);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should choose link below',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.linkGrid();
|
||||
var $appLink = $('#a_1'),
|
||||
$expect = $('#a_2'),
|
||||
$res = this.drawer.findAdjacentLink(
|
||||
$appLink, this.drawer.DOWN
|
||||
);
|
||||
assert.equal($res[0].id, $expect[0].id);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should choose first link if next on last',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.linkGrid();
|
||||
var $appLink = $('#b_2'),
|
||||
$expect = $('#a_0'),
|
||||
$res = this.drawer.findAdjacentLink(
|
||||
$appLink, this.drawer.RIGHT
|
||||
);
|
||||
assert.equal($res[0].id, $expect[0].id);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should choose bottom link if up on top',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.linkGrid();
|
||||
var $appLink = $('#a_0'),
|
||||
$expect = $('#a_2'),
|
||||
$res = this.drawer.findAdjacentLink(
|
||||
$appLink, this.drawer.UP
|
||||
);
|
||||
assert.equal($res[0].id, $expect[0].id);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test('It should choose top link if down on bottom',
|
||||
function(assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.linkGrid();
|
||||
var $appLink = $('#a_2'),
|
||||
$expect = $('#a_0'),
|
||||
$res = this.drawer.findAdjacentLink(
|
||||
$appLink, this.drawer.DOWN
|
||||
);
|
||||
assert.equal($res[0].id, $expect[0].id);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
|
@ -1,2 +1 @@
|
|||
from . import test_ui
|
||||
from . import test_res_users
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# Copyright 2016 LasLabs Inc.
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo.tests import HttpCase
|
||||
|
||||
|
||||
class TestUi(HttpCase):
|
||||
|
||||
def test_ui_web(self):
|
||||
"""Test backend tests."""
|
||||
self.phantom_js(
|
||||
"/web/tests?module=web_responsive",
|
||||
"",
|
||||
login="admin",
|
||||
)
|
|
@ -10,46 +10,11 @@
|
|||
<template id="assets_backend" name="Open Mobile Assets" inherit_id="web.assets_backend">
|
||||
<xpath expr=".">
|
||||
<link rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/web_responsive/static/lib/css/drawer.3.2.2.css"
|
||||
href="/web_responsive/static/src/css/web_responsive.scss"
|
||||
/>
|
||||
<link rel="stylesheet"
|
||||
href="/web_responsive/static/src/less/main.less"
|
||||
/>
|
||||
<link rel="stylesheet"
|
||||
href="/web_responsive/static/src/less/navbar.less"
|
||||
/>
|
||||
<link rel="stylesheet"
|
||||
href="/web_responsive/static/src/less/app_drawer.less"
|
||||
/>
|
||||
<link rel="stylesheet"
|
||||
href="/web_responsive/static/src/less/form_view.less"
|
||||
/>
|
||||
<link rel="stylesheet"
|
||||
href="/web_responsive/static/src/less/variables.less"
|
||||
/>
|
||||
<script type="application/javascript"
|
||||
src="/web_responsive/static/lib/js/bililiteRange.2.6.js"
|
||||
/>
|
||||
<script type="application/javascript"
|
||||
src="/web_responsive/static/lib/js/jquery.sendkeys.4.js"
|
||||
/>
|
||||
<script type="application/javascript"
|
||||
src="/web_responsive/static/lib/js/iscroll-probe.5.2.0.js"
|
||||
/>
|
||||
<script type="application/javascript"
|
||||
src="/web_responsive/static/lib/js/drawer.3.2.2.js"
|
||||
/>
|
||||
<script type="application/javascript"
|
||||
src="/web_responsive/static/src/js/web_responsive.js"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="qunit_suite" inherit_id="web.qunit_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="application/javascript"
|
||||
src="/web_responsive/static/tests/js/web_responsive.js"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
Copyright 2016 LasLabs Inc.
|
||||
Copyright 2018 Alexandre Díaz
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
-->
|
||||
|
@ -12,299 +11,9 @@
|
|||
inherit_id="web.webclient_bootstrap"
|
||||
name="App Drawer - Web Client"
|
||||
>
|
||||
|
||||
<xpath expr="//div[hasclass('o_sub_menu')]" position="replace" />
|
||||
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//nav[@id='oe_main_menu_navbar']" position="replace">
|
||||
|
||||
<t t-set="body_classname" t-value="'drawer drawer--left o_web_client'" />
|
||||
|
||||
<header role="banner">
|
||||
<nav id="odooAppDrawer" class="app-drawer-nav drawer-nav" role="navigation">
|
||||
<t t-call="web.menu" />
|
||||
</nav>
|
||||
|
||||
<nav class="navbar navbar-default main-nav"
|
||||
role="navigation"
|
||||
groups="base.group_user,base.group_portal"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="navbar-header">
|
||||
|
||||
<a class="drawer-toggle navbar-collapse collapse btn btn-default app-drawer-toggle"
|
||||
accesskey="A"
|
||||
>
|
||||
<span class="sr-only">Toggle App Drawer</span>
|
||||
<i class="fa fa-th fa-lg app-drawer-icon-open"
|
||||
t-translation="off"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<button type="button"
|
||||
class="app-drawer-toggle drawer-toggle pull-left navbar-toggle collapsed"
|
||||
>
|
||||
<span class="sr-only">Toggle App Drawer</span>
|
||||
<div class="fa fa-th fa-lg app-drawer-icon-open" />
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
id="odooMenuBarToggle"
|
||||
class="navbar-toggle collapsed pull-right"
|
||||
data-toggle="collapse"
|
||||
data-target="#odooMenuBarNav"
|
||||
>
|
||||
<span class="sr-only">Toggle Navigation</span>
|
||||
<i class="fa fa-bars fa-lg"
|
||||
t-translation="off"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse"
|
||||
id="odooMenuBarNav"
|
||||
data-parent="#odooMenuBarToggle"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<div class="o_sub_menu"
|
||||
groups="base.group_user,base.group_portal"
|
||||
>
|
||||
<t t-call="web.menu_secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav navbar-nav navbar-right navbar-systray o_menu_systray">
|
||||
<ul class="nav navbar-nav navbar-right navbar-systray-item oe_user_menu_placeholder"/>
|
||||
<ul class="nav navbar-nav navbar-right navbar-systray-item oe_systray"/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</header>
|
||||
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('o_main')]" position="attributes">
|
||||
<xpath expr="//*[hasclass('o_main')]" position="attributes">
|
||||
<attribute name="t-attf-class">o_main o_chatter_position_{{ request.env.user.chatter_position or 'normal' }}</attribute>
|
||||
</xpath>
|
||||
|
||||
</template>
|
||||
|
||||
<template id="menu_secondary"
|
||||
inherit_id="web.menu_secondary"
|
||||
name="App Drawer - Secondary Menu"
|
||||
>
|
||||
|
||||
<xpath expr="//div[hasclass('o_sub_menu_content')]/t" position="replace">
|
||||
|
||||
<t t-foreach="menu_data['children']" t-as="menu">
|
||||
<ul style="display: none"
|
||||
class="oe_secondary_menu nav navbar-nav"
|
||||
t-att-data-menu-parent="menu['id']">
|
||||
<li class="app-name">
|
||||
<span class="oe_menu_text">
|
||||
<t t-esc="menu['name']"/>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<t t-call="web.menu_secondary_submenu" />
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
|
||||
</xpath>
|
||||
|
||||
</template>
|
||||
|
||||
<template id="menu_secondary_submenu"
|
||||
inherit_id="web.menu_secondary_submenu"
|
||||
name="App Drawer - Secondary Submenu"
|
||||
>
|
||||
|
||||
<xpath expr="//ul" position="replace">
|
||||
|
||||
<t t-foreach="menu['children']" t-as="menu">
|
||||
<t t-if="menu['children']">
|
||||
<li t-attf-class="{{ 'dropdown-header' if submenu else '' }}">
|
||||
<t t-if="submenu">
|
||||
<t t-esc="menu['name']" />
|
||||
<t t-call="web.menu_secondary_submenu">
|
||||
<t t-set="submenu" t-value="True" />
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="not submenu">
|
||||
<a class="dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
role="button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<t t-esc="menu['name']" />
|
||||
<span class="caret" />
|
||||
</a>
|
||||
<ul t-if="menu['children']"
|
||||
t-attf-class="dropdown-menu oe_secondary_submenu dropdown-scrollable"
|
||||
>
|
||||
<t t-call="web.menu_secondary_submenu">
|
||||
<t t-set="submenu" t-value="True" />
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
</li>
|
||||
</t>
|
||||
<t t-if="not menu['children']">
|
||||
<li>
|
||||
<t t-call="web.menu_link" />
|
||||
</li>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</xpath>
|
||||
|
||||
</template>
|
||||
|
||||
<template id="menu_link"
|
||||
inherit_id="web.menu_link"
|
||||
name="App Drawer - Menu Link"
|
||||
>
|
||||
|
||||
<xpath expr="//a" position="attributes">
|
||||
<attribute name="t-att-data-menu-name">menu['name']</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//span[hasclass('oe_menu_text')]" position="replace">
|
||||
<t t-if="display_images">
|
||||
<img t-attf-src="/web/image/ir.ui.menu/{{ menu['id'] }}/web_icon_data"
|
||||
class="app-drawer-icon-app img-rounded"
|
||||
t-att-alt="menu['name']"
|
||||
t-att-title="menu['name']"
|
||||
/>
|
||||
<p class="app-drawer-title text-center">
|
||||
<t t-esc="menu['name']" />
|
||||
</p>
|
||||
</t>
|
||||
<t t-if="not display_images">
|
||||
<span class="oe_menu_text">
|
||||
<t t-esc="menu['name']" />
|
||||
</span>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
</template>
|
||||
|
||||
<template id="menu"
|
||||
inherit_id="web.menu"
|
||||
name="App Drawer - Menu"
|
||||
>
|
||||
|
||||
<xpath expr="//ul[hasclass('oe_systray')]" position="replace" />
|
||||
|
||||
<xpath expr="//ul[hasclass('oe_user_menu_placeholder')]" position="replace" />
|
||||
|
||||
<xpath expr="//ul[hasclass('oe_application_menu_placeholder')]" position="replace">
|
||||
|
||||
<div class="panel-default app-drawer-app-panel" id="appDrawerAppMenu">
|
||||
<div class="panel-heading" id="appDrawerAppPanelHead">
|
||||
<div class="col-xs-6">
|
||||
<h4 class="app-drawer-panel-title pull-left">
|
||||
<a class="app-drawer-icon-close drawer-toggle hidden-xs">
|
||||
<i class="fa fa-lg fa-chevron-left"
|
||||
t-translation="off"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Apps
|
||||
</a>
|
||||
<span class="hidden-xs">
|
||||
&nbsp;|&nbsp;
|
||||
</span>
|
||||
<a class="app-drawer-icon-search drawer-search-open">
|
||||
<i class="fa fa-lg fa-search"
|
||||
t-translation="off"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
<a class="app-drawer-icon-search drawer-search-close">
|
||||
<i class="fa fa-lg fa-close"
|
||||
t-translation="off"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<a class="oe_logo pull-right" t-attf-href="/web?{{ keep_query() }}">
|
||||
<i class="fa fa-pencil-square-o oe_logo_edit"
|
||||
aria-hidden="true"
|
||||
t-translation="off"
|
||||
/>
|
||||
<img src='/web/binary/company_logo'/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body" id="appDrawerAppPanelBody">
|
||||
<div id="appDrawerApps"
|
||||
class="row oe_application_menu_placeholder"
|
||||
style="display: none;">
|
||||
<t t-foreach="menu_data['children']" t-as="menu">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center mt16">
|
||||
<t t-call="web.menu_link">
|
||||
<t t-set="display_images" t-value="1" />
|
||||
</t>
|
||||
</div>
|
||||
<!-- Provide breakpoints on necessary viewports for proper row heights -->
|
||||
<t t-if="(menu_index + 1) % 6 == 0">
|
||||
<div class="clearfix visible-lg-block" />
|
||||
</t>
|
||||
<t t-if="(menu_index + 1) % 4 == 0">
|
||||
<div class="clearfix visible-md-block" />
|
||||
</t>
|
||||
<t t-if="(menu_index + 1) % 3 == 0">
|
||||
<div class="clearfix visible-sm-block" />
|
||||
</t>
|
||||
<t t-if="(menu_index + 1) % 2 == 0">
|
||||
<div class="clearfix visible-xs-block" />
|
||||
</t>
|
||||
</t>
|
||||
<div id="menu_more_container" class="dropdown" style="display: none;">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
|
||||
<ul id="menu_more" class="dropdown-menu"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="appDrawerMenuSearch"
|
||||
class="row list-unstyled"
|
||||
style="display: none;">
|
||||
<div class="panel panel-info app-drawer-search-panel">
|
||||
<div class="panel-heading">
|
||||
<h2>
|
||||
<i>Searching:</i>
|
||||
<input id="appDrawerSearchInput" class="menu-search-query"/>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul id="appDrawerSearchResults"
|
||||
class="row list-unstyled oe_application_menu_placeholder">
|
||||
No Search Supplied.
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-drawer-search-action" />
|
||||
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
|
Loading…
Reference in New Issue