Merge pull request #596 from expsa/upload_modules

Upload modules
This commit is contained in:
enagahh 2024-08-08 12:28:02 +03:00 committed by GitHub
commit 18cdd307d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
228 changed files with 112341 additions and 0 deletions

View File

@ -0,0 +1,215 @@
==============
Web Responsive
==============
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d0b60dc922a2a476408a3f629da66e4b6c9469506f073e3c88976820f1a70f43
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
:target: https://odoo-community.org/page/development-status
:alt: Production/Stable
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
: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/14.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-14-0/web-14-0-web_responsive
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=14.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module adds responsiveness to web backend.
Features for all devices:
* 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
* Increase the size of the labels in extra large screens
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/label_size_small.png
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/label_size_large.png
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
* Search panel is hidden on small screens.
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/search_panel.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
* Sticky chatter topbar
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/chatter_topbar.gif
* AppMenu waits for action finished to show the view
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/appmenu.gif
* Sticky header & footer in list view
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/listview.gif
* Sticky statusbar in form view
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/formview.gif
* Followers and send button is displayed on mobile. Avatar is hidden.
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/chatter.gif
* When the chatter is configured on the side part, the document viewer fills that
part for side-by-side reading instead of full screen. You can still put it on full
width preview clicking on the new maximize button.
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/document_viewer.gif
* Bigger checkboxes in list view
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/big_checkboxes.gif
* Scrollable dropdowns
.. image:: https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/dropdown_scroll.gif
**Table of contents**
.. contents::
:local:
Usage
=====
The following keyboard shortcuts are implemented:
* Navigate app search results - Arrow keys
* Choose app result - ``Enter``
* ``Esc`` to close app drawer
Known issues / Roadmap
======================
* 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.
* Handle long titles on forms in a better way
* Standard sticky headers seems to not work properly on iOS Safari/Chrome (see #1626).
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 to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2014.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.
Credits
=======
Authors
~~~~~~~
* LasLabs
* Tecnativa
Contributors
~~~~~~~~~~~~
* Dave Lasley <dave@laslabs.com>
* Jairo Llopis <jairo.llopis@tecnativa.com>
* Dennis Sluijk <d.sluijk@onestein.nl>
* Sergio Teruel <sergio.teruel@tecnativa.com>
* Alexandre Díaz <dev@redneboa.es>
* Mathias Markl <mathias.markl@mukit.at>
* Iván Todorovich <ivan.todorovich@gmail.com>
* Sergey Shebanin <sergey@shebanin.ru>
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
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.
.. |maintainer-Yajo| image:: https://github.com/Yajo.png?size=40px
:target: https://github.com/Yajo
:alt: Yajo
.. |maintainer-Tardo| image:: https://github.com/Tardo.png?size=40px
:target: https://github.com/Tardo
:alt: Tardo
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-Yajo| |maintainer-Tardo|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/14.0/web_responsive>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,30 @@
# Copyright 2016-2017 LasLabs Inc.
# Copyright 2017-2018 Tecnativa - Jairo Llopis
# Copyright 2018-2019 Tecnativa - Alexandre Díaz
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
{
"name": "Web Responsive",
"summary": "Responsive web client, community-supported",
"version": "14.0.1.2.2",
"category": "Website",
"website": "https://github.com/OCA/web",
"author": "LasLabs, Tecnativa, " "Odoo Community Association (OCA)",
"license": "LGPL-3",
"installable": True,
"depends": ["web", "mail"],
"development_status": "Production/Stable",
"maintainers": ["Yajo", "Tardo"],
"data": ["views/assets.xml", "views/res_users.xml", "views/web.xml"],
"qweb": [
"static/src/xml/apps.xml",
"static/src/xml/form_buttons.xml",
"static/src/xml/menu.xml",
"static/src/xml/navbar.xml",
"static/src/xml/attachment_viewer.xml",
"static/src/xml/discuss.xml",
"static/src/xml/control_panel.xml",
"static/src/xml/search_panel.xml",
],
"sequence": 1,
}

View File

@ -0,0 +1,200 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2020-11-07 15:08+0000\n"
"Last-Translator: Waleed Mohsen <Mohsen.Waleed@gmail.com>\n"
"Language-Team: none\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 3.10\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: 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_buttons.xml:0
#, python-format
msgid "Create"
msgstr "إنشاء"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "تجاهل"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "تعديل"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "تكبير"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "تصغير"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "عادي"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "اجراءات سريعة"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "حفظ"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "بحث في القوائم..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "جانبي"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "المستخدمون"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#~ msgstr "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#, python-format
#~ msgid "Close"
#~ msgstr "إغلاق"
#, python-format
#~ msgid ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#~ msgstr ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#, python-format
#~ msgid "false"
#~ msgstr "false"
#, python-format
#~ msgid ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
#~ msgstr ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"

View File

@ -0,0 +1,199 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2022-04-13 11:05+0000\n"
"Last-Translator: Noel estudillo <noelestudilloviera@gmail.com>\n"
"Language-Team: none\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Posició del chatter"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Crear"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Descartar"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Editar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Maximitzar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Minimitzar"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Accions ràpides"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Guardar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Cercar menús..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr "Shift"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Lateral"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Usuaris"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#~ msgstr "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#, python-format
#~ msgid "Close"
#~ msgstr "Tancar"
#, python-format
#~ msgid ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#~ msgstr ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#, python-format
#~ msgid "false"
#~ msgstr "fals"
#, python-format
#~ msgid ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
#~ msgstr ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"

View File

@ -0,0 +1,191 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2018-09-02 05:11+0000\n"
"Last-Translator: Hans Henrik Gabelgaard <hhg@gabelgaard.org>\n"
"Language-Team: none\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.1.1\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Log position"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Side"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Brugere"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#~ msgid "<span class=\"sr-only\">Toggle App Drawer</span>"
#~ msgstr "<span class=\"sr-only\">Skift App skuffe</span>"
#~ msgid "<span class=\"sr-only\">Toggle Navigation</span>"
#~ msgstr "<span class=\"sr-only\">Skift navigation</span>"
#~ msgid "Apps"
#~ msgstr "Applikationer"
#~ msgid "More"
#~ msgstr "Mere"
#~ msgid "More <b class=\"caret\"/>"
#~ msgstr "Mere <b class=\"caret\"/>"
#~ msgid "Task"
#~ msgstr "Opgave"

View File

@ -0,0 +1,192 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
# Translators:
# Niki Waibel <niki.waibel@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-03 01:37+0000\n"
"PO-Revision-Date: 2021-02-18 15:45+0000\n"
"Last-Translator: Janik Vonrotz <janikvonrotz@gmail.com>\n"
"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Chatter-Position"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Erstellen"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Verwerfen"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Ändern"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "maximieren"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "minimieren"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "normal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Schnell-Aktion"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Speichern"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Such-Menü"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Seitlich"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Benutzer"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "Close"
#~ msgstr "Schließen"
#~ msgid "<span class=\"sr-only\">Toggle App Drawer</span>"
#~ msgstr "<span class=\"sr-only\">App Ordner umschalten</span>"
#~ msgid "<span class=\"sr-only\">Toggle Navigation</span>"
#~ msgstr "<span class=\"sr-only\">Navigation umschalten</span>"
#~ msgid "Apps"
#~ msgstr "Apps"
#~ msgid "More <b class=\"caret\"/>"
#~ msgstr "Mehr <b class=\"caret\"/>"

View File

@ -0,0 +1,202 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
# Translators:
# Pedro M. Baeza <pedro.baeza@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-10 10:49+0000\n"
"PO-Revision-Date: 2022-11-17 14:45+0000\n"
"Last-Translator: Luis D. Lafaurie <luislafaurie@hotmail.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: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Posición del chatter"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Crear"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Descartar"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Editar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Maximizar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Minimizar"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Acciones rápidas"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Guardar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Buscar menús..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr "Atajo"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Lateral"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Usuarios"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#~ msgstr "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#, python-format
#~ msgid "Close"
#~ msgstr "Cerrar"
#, python-format
#~ msgid ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#~ msgstr ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#, python-format
#~ msgid "false"
#~ msgstr "falso"
#, python-format
#~ msgid ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
#~ msgstr ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"

View File

@ -0,0 +1,173 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-09-23 07:34+0000\n"
"Last-Translator: Saeed Raeisi <saeed.raesi2020@gmail.com>\n"
"Language-Team: none\n"
"Language: fa\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr "همه"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr "پاک کردن"
#. module: web_responsive
#: 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_buttons.xml:0
#, python-format
msgid "Create"
msgstr "ایجاد"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "لغو"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr "نام نمایشی"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "ویرایش"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr "فیلتر"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr "شناسه"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr "آخرین ویرایش در"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "بزرگ کردن"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "کوچک کردن"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "عادی"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "اقدامات سریع"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr "مشاهده نتیجه"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "ذخیره"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "جستجو در منو..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr "جستجو..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr "شیفت"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "طرف"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr "امروز"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr "تعریف نشده"
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "کاربران"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr "تعویض کننده نما"

View File

@ -0,0 +1,171 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: fa_IR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: 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_buttons.xml:0
#, python-format
msgid "Create"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""

View File

@ -0,0 +1,199 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-09-25 08:34+0000\n"
"Last-Translator: Rémi <remi@le-filament.com>\n"
"Language-Team: none\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Position du Chatter"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Créer"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Annuler"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Modifier"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Maximiser"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Minimiser"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Actions rapides"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Sauvegarder"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Rechercher dans les menus..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, fuzzy, python-format
msgid "Shift"
msgstr "Shift"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "À coté"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Utilisateurs"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#~ msgstr "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#, python-format
#~ msgid "Close"
#~ msgstr "Fermer"
#, python-format
#~ msgid ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#~ msgstr ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#, fuzzy, python-format
#~ msgid "false"
#~ msgstr "false"
#, python-format
#~ msgid ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
#~ msgstr ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"

View File

@ -0,0 +1,173 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-04-11 18:46+0000\n"
"Last-Translator: Yves Le Doeuff <yld@alliasys.fr>\n"
"Language-Team: none\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Position du Chatter"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Créer"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Annuler"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Editer"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Maximiser"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Minimiser"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Actions rapides"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Sauvegarder"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Rechercher dans les menus..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "À coté"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Utilisateurs"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""

View File

@ -0,0 +1,193 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
# Translators:
# Bole <bole@dajmi5.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-04-28 18:03+0000\n"
"PO-Revision-Date: 2022-08-25 13:07+0000\n"
"Last-Translator: Bole <bole@dajmi5.com>\n"
"Language-Team: Croatian (https://www.transifex.com/oca/teams/23907/hr/)\n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Pozicija poruka"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Kreiraj"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Odbaci"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Uredi"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Povećaj"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Smanji"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normalno"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Brze radnje"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Spremi"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Pretraži izbornike..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr "Pomakni"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Sastrane"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Korisnici"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "Close"
#~ msgstr "Zatvori"
#~ msgid "<span class=\"sr-only\">Toggle App Drawer</span>"
#~ msgstr "<span class=\"sr-only\">Izmjeni izbornik aplikacije</span>"
#~ msgid "<span class=\"sr-only\">Toggle Navigation</span>"
#~ msgstr "<span class=\"sr-only\">Izmjeni navigaciju</span>"
#~ msgid "Apps"
#~ msgstr "Apikacije"
#~ msgid "More <b class=\"caret\"/>"
#~ msgstr "Više <b class=\"caret\"/>"

View File

@ -0,0 +1,173 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-02-12 10:45+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr "Tutti"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr "CANCELLA"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Posizione conversazione"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Crea"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Abbandona"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Modifica"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr "FILTRA"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr "ID"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Massimizza"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Minimizza"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Ordinaria"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Azioni rapide"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr "VEDI RISULTATO"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Salva"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Menù ricerca..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr "Cerca..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr "Maiusc"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Laterale"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr "Oggi"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr "Indefinito"
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Utenti"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr "Selettore vista"

View File

@ -0,0 +1,185 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-05-17 20:47+0000\n"
"Last-Translator: Bosd <c5e2fd43-d292-4c90-9d1f-74ff3436329a@anonaddy.me>\n"
"Language-Team: none\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Chatter Positie"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Aanmaken"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Negeren"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Bewerken"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Maximaliseren"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Minimaliseren"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normaal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Snelle Acties"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Opslaan"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Zoek menu's..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, fuzzy, python-format
msgid "Shift"
msgstr "Shift"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Aan de zijkant"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Gebruikers"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#~ msgstr "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#, python-format
#~ msgid "Close"
#~ msgstr "Sluit"
#, fuzzy, python-format
#~ msgid "false"
#~ msgstr "false"

View File

@ -0,0 +1,173 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2019-08-12 11:44+0000\n"
"Last-Translator: Pedro Castro Silva <pedrocs@exo.pt>\n"
"Language-Team: none\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.7.1\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Posição do Chatter"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Criar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Descartar"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Editar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Ações rápidas"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Gravar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Procurar menus..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Lateralizado"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Utilizadores"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""

View File

@ -0,0 +1,211 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
# Translators:
# Rodrigo de Almeida Sottomaior Macedo <rmsolucoeseminformatic4@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-22 08:27+0000\n"
"PO-Revision-Date: 2024-05-21 00:15+0000\n"
"Last-Translator: Rodrigo Macedo <sottomaiormacedotec@users.noreply."
"translation.odoo-community.org>\n"
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/oca/teams/"
"23907/pt_BR/)\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__chatter_position
msgid "Chatter Position"
msgstr "Posição do Chatter"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Criar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Descartar"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr "Exibir Nome"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Editar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr "Identificação"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr "Última Modificação em"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Maximizar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Minimizar"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Normal"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Ações rápidas"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Salvar"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Pesquisar menus..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Frente e verso"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Usuários"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#~ msgstr "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#, python-format
#~ msgid "Close"
#~ msgstr "Fechar"
#, python-format
#~ msgid "false"
#~ msgstr "falso"
#, python-format
#~ msgid ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
#~ msgstr ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
#~ msgid "<span class=\"sr-only\">Toggle App Drawer</span>"
#~ msgstr "<span class=\"sr-only\">Aplicativo Desenhador Alternativo</span>"
#~ msgid "<span class=\"sr-only\">Toggle Navigation</span>"
#~ msgstr "<span class=\"sr-only\">Navegação Alternativa</span>"
#~ msgid "Apps"
#~ msgstr "Aplicativos"
#~ msgid "HTTP routing"
#~ msgstr "roteamento HTTP"
#~ msgid "More <b class=\"caret\"/>"
#~ msgstr "Mais <b class=\"caret\"/>"

View File

@ -0,0 +1,174 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-03-31 16:36+0000\n"
"Last-Translator: SplashS <sergey@shebanin.ru>\n"
"Language-Team: none\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr "Все"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr "Очистить"
#. module: web_responsive
#: 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_buttons.xml:0
#, python-format
msgid "Create"
msgstr "Создать"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "Отменить"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr "Видимое имя"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "Править"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr "Фильтр"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "Увеличить"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "Уменьшить"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "Снизу"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "Действия"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr "Применить"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "Сохранить"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "Поиск по меню..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr "Поиск..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "Справа"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr "Сегодня"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr "Не определено"
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "Пользователи"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr "Сменить вид"

View File

@ -0,0 +1,199 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_responsive
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2020-07-08 05:19+0000\n"
"Last-Translator: 黎伟杰 <674416404@qq.com>\n"
"Language-Team: none\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.10\n"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
#: 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_buttons.xml:0
#, python-format
msgid "Create"
msgstr "创建"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr "丢弃"
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__display_name
msgid "Display Name"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr "编辑"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users__id
msgid "ID"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields,field_description:web_responsive.field_res_users____last_update
msgid "Last Modified on"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr "最大化"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr "最小化"
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__normal
msgid "Normal"
msgstr "正常"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr "快捷方式"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr "保存"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#, python-format
msgid "Search menus..."
msgstr "搜索菜单..."
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
#, python-format
msgid "Shift"
msgstr ""
#. module: web_responsive
#: model:ir.model.fields.selection,name:web_responsive.selection__res_users__chatter_position__sided
msgid "Sided"
msgstr "侧面"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
msgstr "用户"
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "View switcher"
msgstr ""
#, python-format
#~ msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#~ msgstr "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
#, python-format
#~ msgid "Close"
#~ msgstr "关闭"
#, python-format
#~ msgid ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#~ msgstr ""
#~ "btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-"
#~ "none"
#, python-format
#~ msgid "false"
#~ msgstr "false"
#, python-format
#~ msgid ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
#~ msgstr ""
#~ "modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"

View File

@ -0,0 +1 @@
from . import res_users

View File

@ -0,0 +1,27 @@
# Copyright 2018-2019 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResUsers(models.Model):
_inherit = "res.users"
chatter_position = fields.Selection(
[("normal", "Normal"), ("sided", "Sided")],
string="Chatter Position",
default="sided",
)
def __init__(self, pool, cr):
"""Override of __init__ to add access rights.
Access rights are disabled by default, but allowed on some specific
fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS.
"""
super().__init__(pool, cr)
# duplicate list to avoid modifying the original reference
type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
type(self).SELF_WRITEABLE_FIELDS.extend(["chatter_position"])
# duplicate list to avoid modifying the original reference
type(self).SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS)
type(self).SELF_READABLE_FIELDS.extend(["chatter_position"])

View File

@ -0,0 +1,8 @@
* Dave Lasley <dave@laslabs.com>
* Jairo Llopis <jairo.llopis@tecnativa.com>
* Dennis Sluijk <d.sluijk@onestein.nl>
* Sergio Teruel <sergio.teruel@tecnativa.com>
* Alexandre Díaz <dev@redneboa.es>
* Mathias Markl <mathias.markl@mukit.at>
* Iván Todorovich <ivan.todorovich@gmail.com>
* Sergey Shebanin <sergey@shebanin.ru>

View File

@ -0,0 +1,101 @@
This module adds responsiveness to web backend.
Features for all devices:
* 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
* Increase the size of the labels in extra large screens
.. image:: ../static/img/label_size_small.png
.. image:: ../static/img/label_size_large.png
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
* Search panel is hidden on small screens.
.. image:: ../static/img/search_panel.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
* Sticky chatter topbar
.. image:: ../static/img/chatter_topbar.gif
* AppMenu waits for action finished to show the view
.. image:: ../static/img/appmenu.gif
* Sticky header & footer in list view
.. image:: ../static/img/listview.gif
* Sticky statusbar in form view
.. image:: ../static/img/formview.gif
* Followers and send button is displayed on mobile. Avatar is hidden.
.. image:: ../static/img/chatter.gif
* When the chatter is configured on the side part, the document viewer fills that
part for side-by-side reading instead of full screen. You can still put it on full
width preview clicking on the new maximize button.
.. image:: ../static/img/document_viewer.gif
* Bigger checkboxes in list view
.. image:: ../static/img/big_checkboxes.gif
* Scrollable dropdowns
.. image:: ../static/img/dropdown_scroll.gif

View File

@ -0,0 +1,7 @@
* 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.
* Handle long titles on forms in a better way
* Standard sticky headers seems to not work properly on iOS Safari/Chrome (see #1626).

View File

@ -0,0 +1,5 @@
The following keyboard shortcuts are implemented:
* Navigate app search results - Arrow keys
* Choose app result - ``Enter``
* ``Esc`` to close app drawer

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,534 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Web Responsive</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="web-responsive">
<h1 class="title">Web Responsive</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d0b60dc922a2a476408a3f629da66e4b6c9469506f073e3c88976820f1a70f43
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" 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 image-reference" href="https://github.com/OCA/web/tree/14.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 image-reference" href="https://translation.odoo-community.org/projects/web-14-0/web-14-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 image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=14.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-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>
<li><p class="first">Increase the size of the labels in extra large screens</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/label_size_small.png" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/label_size_small.png" />
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/label_size_large.png" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/label_size_large.png" />
</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>
<li><p class="first">Search panel is hidden on small screens.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/search_panel.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/search_panel.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>
<li><p class="first">Sticky chatter topbar</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/chatter_topbar.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/chatter_topbar.gif" />
</li>
<li><p class="first">AppMenu waits for action finished to show the view</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/appmenu.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/appmenu.gif" />
</li>
<li><p class="first">Sticky header &amp; footer in list view</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/listview.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/listview.gif" />
</li>
<li><p class="first">Sticky statusbar in form view</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/formview.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/formview.gif" />
</li>
<li><p class="first">Followers and send button is displayed on mobile. Avatar is hidden.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/chatter.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/chatter.gif" />
</li>
<li><p class="first">When the chatter is configured on the side part, the document viewer fills that
part for side-by-side reading instead of full screen. You can still put it on full
width preview clicking on the new maximize button.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/document_viewer.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/document_viewer.gif" />
</li>
<li><p class="first">Bigger checkboxes in list view</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/big_checkboxes.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/big_checkboxes.gif" />
</li>
<li><p class="first">Scrollable dropdowns</p>
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/dropdown_scroll.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_responsive/static/img/dropdown_scroll.gif" />
</li>
</ul>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>The following keyboard shortcuts are implemented:</p>
<ul class="simple">
<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="#toc-entry-2">Known issues / Roadmap</a></h1>
<ul class="simple">
<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 Odoos own limitation.</li>
<li>App navigation with keyboard.</li>
<li>Handle long titles on forms in a better way</li>
<li>Standard sticky headers seems to not work properly on iOS Safari/Chrome (see #1626).</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<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 to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2014.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">
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>LasLabs</li>
<li>Tecnativa</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul class="simple">
<li>Dave Lasley &lt;<a class="reference external" href="mailto:dave&#64;laslabs.com">dave&#64;laslabs.com</a>&gt;</li>
<li>Jairo Llopis &lt;<a class="reference external" href="mailto:jairo.llopis&#64;tecnativa.com">jairo.llopis&#64;tecnativa.com</a>&gt;</li>
<li>Dennis Sluijk &lt;<a class="reference external" href="mailto:d.sluijk&#64;onestein.nl">d.sluijk&#64;onestein.nl</a>&gt;</li>
<li>Sergio Teruel &lt;<a class="reference external" href="mailto:sergio.teruel&#64;tecnativa.com">sergio.teruel&#64;tecnativa.com</a>&gt;</li>
<li>Alexandre Díaz &lt;<a class="reference external" href="mailto:dev&#64;redneboa.es">dev&#64;redneboa.es</a>&gt;</li>
<li>Mathias Markl &lt;<a class="reference external" href="mailto:mathias.markl&#64;mukit.at">mathias.markl&#64;mukit.at</a>&gt;</li>
<li>Iván Todorovich &lt;<a class="reference external" href="mailto:ivan.todorovich&#64;gmail.com">ivan.todorovich&#64;gmail.com</a>&gt;</li>
<li>Sergey Shebanin &lt;<a class="reference external" href="mailto:sergey&#64;shebanin.ru">sergey&#64;shebanin.ru</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<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>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainers</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/Yajo"><img alt="Yajo" src="https://github.com/Yajo.png?size=40px" /></a> <a class="reference external image-reference" href="https://github.com/Tardo"><img alt="Tardo" src="https://github.com/Tardo.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/14.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>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 MiB

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="2000" height="1128" viewBox="0 0 2000 1128">
<polygon fill-opacity=".03" points="0 1077.844 392.627 778.443 1504.99 1127.745 0 1127.745"/>
<polygon fill-opacity=".02" points="392.216 778.443 283.294 0 0 0 0 666.504"/>
<polygon fill-opacity=".03" points="1000 0 2000 1009.98 2000 439.94 1749.817 0"/>
</svg>

After

Width:  |  Height:  |  Size: 366 B

View File

@ -0,0 +1,532 @@
odoo.define("web_responsive.KanbanRendererMobile", function (require) {
"use strict";
/**
* The purpose of this file is to improve the UX of grouped kanban views in
* mobile. It includes the KanbanRenderer (in mobile only) to only display one
* column full width, and enables the swipe to browse to the other columns.
* Moreover, records in columns are lazy-loaded.
*/
var config = require("web.config");
var core = require("web.core");
var KanbanRenderer = require("web.KanbanRenderer");
var KanbanView = require("web.KanbanView");
var KanbanQuickCreate = require("web.kanban_column_quick_create");
var _t = core._t;
var qweb = core.qweb;
if (!config.device.isMobile) {
return;
}
KanbanQuickCreate.include({
init() {
this._super.apply(this, arguments);
this.isMobile = true;
},
/**
* KanbanRenderer will decide can we close quick create or not
* @private
* @override
*/
_cancel: function () {
this.trigger_up("close_quick_create");
},
/**
* Clear input when showed
* @override
*/
toggleFold: function () {
this._super.apply(this, arguments);
if (!this.folded) {
this.$input.val("");
}
},
});
KanbanView.include({
init() {
this._super.apply(this, arguments);
this.jsLibs.push("/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js");
},
});
KanbanRenderer.include({
custom_events: _.extend({}, KanbanRenderer.prototype.custom_events || {}, {
quick_create_column_created: "_onColumnAdded",
}),
events: _.extend({}, KanbanRenderer.prototype.events, {
"click .o_kanban_mobile_tab": "_onMobileTabClicked",
"click .o_kanban_mobile_add_column": "_onMobileQuickCreateClicked",
}),
// Allows to disable animations for the tests
ANIMATE: true,
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
// Index of the currently displayed column
this.activeColumnIndex = 0;
this._scrollPosition = null;
},
/**
* As this renderer defines its own scrolling area (the column in grouped
* mode), we override this hook to restore the scroll position like it was
* when the renderer has been last detached.
*
* @override
*/
on_attach_callback: function () {
if (
this._scrollPosition &&
this.state.groupedBy.length &&
this.widgets.length
) {
var $column = this.widgets[this.activeColumnIndex].$el;
$column.scrollLeft(this._scrollPosition.left);
$column.scrollTop(this._scrollPosition.top);
}
this._computeTabPosition();
this._super.apply(this, arguments);
},
/**
* As this renderer defines its own scrolling area (the column in grouped
* mode), we override this hook to store the scroll position, so that we can
* restore it if the renderer is re-attached to the DOM later.
*
* @override
*/
on_detach_callback: function () {
if (this.state.groupedBy.length && this.widgets.length) {
var $column = this.widgets[this.activeColumnIndex].$el;
this._scrollPosition = {
left: $column.scrollLeft(),
top: $column.scrollTop(),
};
} else {
this._scrollPosition = null;
}
this._super.apply(this, arguments);
},
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
/**
* Displays the quick create record in the active column
* override to open quick create record in current active column
*
* @override
* @returns {Promise}
*/
addQuickCreate: function () {
if (this._canCreateColumn() && !this.quickCreate.folded) {
this._onMobileQuickCreateClicked();
}
return this.widgets[this.activeColumnIndex].addQuickCreate();
},
/**
* Overrides to restore the left property and the scrollTop on the updated
* column, and to enable the swipe handlers
*
* @override
*/
updateColumn: function (localID) {
var index = _.findIndex(this.widgets, {db_id: localID});
var $column = this.widgets[index].$el;
var scrollTop = $column.scrollTop();
return (
this._super
.apply(this, arguments)
.then(() => this._layoutUpdate(false))
// Required when clicking on 'Load More'
.then(() => $column.scrollTop(scrollTop))
.then(() => this._enableSwipe())
);
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* Check if we use the quick create on mobile
* @returns {Boolean}
* @private
*/
_canCreateColumn: function () {
return this.quickCreateEnabled && this.quickCreate && this.widgets.length;
},
/**
* Update the columns positions
*
* @private
* @param {Boolean} [animate=false] set to true to animate
*/
_computeColumnPosition: function (animate) {
if (this.widgets.length) {
// Check rtl to compute correct css value
const rtl = _t.database.parameters.direction === "rtl";
// Display all o_kanban_group
this.$(".o_kanban_group").show();
const $columnAfter = this._toNode(
this.widgets.filter(
(widget, index) => index > this.activeColumnIndex
)
);
const promiseAfter = this._updateColumnCss(
$columnAfter,
rtl ? {right: "100%"} : {left: "100%"},
animate
);
const $columnBefore = this._toNode(
this.widgets.filter(
(widget, index) => index < this.activeColumnIndex
)
);
const promiseBefore = this._updateColumnCss(
$columnBefore,
rtl ? {right: "-100%"} : {left: "-100%"},
animate
);
const $columnCurrent = this._toNode(
this.widgets.filter(
(widget, index) => index === this.activeColumnIndex
)
);
const promiseCurrent = this._updateColumnCss(
$columnCurrent,
rtl ? {right: "0%"} : {left: "0%"},
animate
);
promiseAfter
.then(promiseBefore)
.then(promiseCurrent)
.then(() => {
$columnAfter.hide();
$columnBefore.hide();
});
}
},
/**
* Define the o_current class to the current selected kanban (column & tab)
*
* @private
*/
_computeCurrentColumn: function () {
if (this.widgets.length) {
var column = this.widgets[this.activeColumnIndex];
if (!column) {
return;
}
var columnID = column.id || column.db_id;
this.$(
".o_kanban_mobile_tab.o_current, .o_kanban_group.o_current"
).removeClass("o_current");
this.$(
'.o_kanban_group[data-id="' +
columnID +
'"], ' +
'.o_kanban_mobile_tab[data-id="' +
columnID +
'"]'
).addClass("o_current");
}
},
/**
* Update the tabs positions
*
* @private
*/
_computeTabPosition: function () {
this._computeTabJustification();
this._computeTabScrollPosition();
},
/**
* Update the tabs positions
*
* @private
*/
_computeTabScrollPosition: function () {
if (this.widgets.length) {
var lastItemIndex = this.widgets.length - 1;
var moveToIndex = this.activeColumnIndex;
var scrollToLeft = 0;
for (var i = 0; i < moveToIndex; i++) {
var columnWidth = this._getTabWidth(this.widgets[i]);
// Apply
if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) {
var partialWidth = 0.75;
scrollToLeft += columnWidth * partialWidth;
} else {
scrollToLeft += columnWidth;
}
}
// Apply the scroll x on the tabs
// XXX in case of RTL, should we use scrollRight?
this.$(".o_kanban_mobile_tabs").scrollLeft(scrollToLeft);
}
},
/**
* Compute the justify content of the kanban tab headers
*
* @private
*/
_computeTabJustification: function () {
if (this.widgets.length) {
var self = this;
// Use to compute the sum of the width of all tab
var widthChilds = this.widgets.reduce(function (total, column) {
return total + self._getTabWidth(column);
}, 0);
// Apply a space around between child if the parent length is higher then the sum of the child width
var $tabs = this.$(".o_kanban_mobile_tabs");
$tabs.toggleClass(
"justify-content-between",
$tabs.outerWidth() >= widthChilds
);
}
},
/**
* Enables swipe event on the current column
*
* @private
*/
_enableSwipe: function () {
var self = this;
var step = _t.database.parameters.direction === "rtl" ? -1 : 1;
this.$el.swipe({
excludedElements: ".o_kanban_mobile_tabs",
swipeLeft: function () {
var moveToIndex = self.activeColumnIndex + step;
if (moveToIndex < self.widgets.length) {
self._moveToGroup(moveToIndex, self.ANIMATE);
}
},
swipeRight: function () {
var moveToIndex = self.activeColumnIndex - step;
if (moveToIndex > -1) {
self._moveToGroup(moveToIndex, self.ANIMATE);
}
},
});
},
/**
* Retrieve the outerWidth of a given widget column
*
* @param {KanbanColumn} column
* @returns {integer} outerWidth of the found column
* @private
*/
_getTabWidth: function (column) {
var columnID = column.id || column.db_id;
return this.$(
'.o_kanban_mobile_tab[data-id="' + columnID + '"]'
).outerWidth();
},
/**
* Update the kanban layout
*
* @private
* @param {Boolean} [animate=false] set to true to animate
*/
_layoutUpdate: function (animate) {
this._computeCurrentColumn();
this._computeTabPosition();
this._computeColumnPosition(animate);
this._enableSwipe();
},
/**
* Moves to the given kanban column
*
* @private
* @param {integer} moveToIndex index of the column to move to
* @param {Boolean} [animate=false] set to true to animate
* @returns {Promise} resolved when the new current group has been loaded
* and displayed
*/
_moveToGroup: function (moveToIndex, animate) {
if (this.widgets.length === 0) {
return Promise.resolve();
}
var self = this;
if (moveToIndex >= 0 && moveToIndex < this.widgets.length) {
this.activeColumnIndex = moveToIndex;
}
var column = this.widgets[this.activeColumnIndex];
this._enableSwipe();
if (!column.data.isOpen) {
this.trigger_up("column_toggle_fold", {
db_id: column.db_id,
onSuccess: () => self._layoutUpdate(animate),
});
}
if (column.data.isOpen) {
this._layoutUpdate(animate);
}
return Promise.resolve();
},
/**
* @override
* @private
*/
_renderExampleBackground: function () {
// Override to avoid display of example background
},
/**
* @override
* @private
*/
_renderGrouped: function (fragment) {
var self = this;
var newFragment = document.createDocumentFragment();
var fragment_change = [newFragment];
this._super.apply(this, fragment_change);
this.defs.push(
Promise.all(this.defs).then(function () {
var data = [];
_.each(self.state.data, function (group) {
var grp = group;
if (!grp.value) {
grp = _.extend({}, grp, {value: _t("Undefined")});
data.unshift(grp);
}
if (grp.value) {
data.push(grp);
}
});
var kanbanColumnContainer = document.createElement("div");
kanbanColumnContainer.classList.add("o_kanban_columns_content");
kanbanColumnContainer.appendChild(newFragment);
fragment.appendChild(kanbanColumnContainer);
$(
qweb.render("KanbanView.MobileTabs", {
data: data,
quickCreateEnabled: self._canCreateColumn(),
})
).prependTo(fragment);
})
);
},
/**
* @override
* @private
*/
_renderView: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
if (self.state.groupedBy.length) {
// Force first column for kanban view, because the groupedBy can be changed
return self._moveToGroup(0);
}
if (self._canCreateColumn()) {
self._onMobileQuickCreateClicked();
}
return Promise.resolve();
});
},
/**
* Retrieve the Jquery node (.o_kanban_group) for a list of a given widgets
*
* @private
* @param {Object} widgets: ''
* @returns {jQuery} the matching .o_kanban_group widgets
*/
_toNode: function (widgets) {
const selectorCss = widgets
.map(
(widget) =>
'.o_kanban_group[data-id="' + (widget.id || widget.db_id) + '"]'
)
.join(", ");
return this.$(selectorCss);
},
/**
* Update the given column to the updated positions
*
* @private
* @param {Object} $column The jquery column
* @param {Object} cssProperties Use to update column
* @param {Boolean} [animate=false] set to true to animate
* @returns {Promise}
*/
_updateColumnCss: function ($column, cssProperties, animate) {
if (animate) {
return new Promise((resolve) =>
$column.animate(cssProperties, "fast", resolve)
);
}
$column.css(cssProperties);
return Promise.resolve();
},
// --------------------------------------------------------------------------
// Handlers
// --------------------------------------------------------------------------
/**
* @private
*/
_onColumnAdded: function () {
this._computeTabPosition();
if (this._canCreateColumn() && !this.quickCreate.folded) {
this.quickCreate.toggleFold();
}
},
/**
* @private
* @param {MouseEvent} event
*/
_onMobileQuickCreateClicked: function (event) {
if (event) {
event.stopPropagation();
}
this.quickCreate.toggleFold();
this.$(".o_kanban_group").toggle(this.quickCreate.folded);
},
/**
* @private
* @param {MouseEvent} event
*/
_onMobileTabClicked: function (event) {
if (this._canCreateColumn() && !this.quickCreate.folded) {
this.quickCreate.toggleFold();
}
this._moveToGroup($(event.currentTarget).index(), true);
},
/**
* @private
* @override
*/
_onCloseQuickCreate: function () {
if (this.widgets.length && !this.quickCreate.folded) {
this.$(".o_kanban_group").toggle(true);
this.quickCreate.toggleFold();
}
},
});
});

View File

@ -0,0 +1,662 @@
/* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2018 Tecnativa - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive", function (require) {
"use strict";
const ActionManager = require("web.ActionManager");
const AbstractWebClient = require("web.AbstractWebClient");
const AppsMenu = require("web.AppsMenu");
const BasicController = require("web.BasicController");
const config = require("web.config");
const core = require("web.core");
const FormRenderer = require("web.FormRenderer");
const Menu = require("web.Menu");
const RelationalFields = require("web.relational_fields");
const ListRenderer = require("web.ListRenderer");
const CalendarRenderer = require("web.CalendarRenderer");
const patchMixin = require("web.patchMixin");
const AttachmentViewer = require("mail/static/src/components/attachment_viewer/attachment_viewer.js");
const PatchableAttachmentViewer = patchMixin(AttachmentViewer);
const ControlPanel = require("web.ControlPanel");
const SearchPanel = require("web/static/src/js/views/search_panel.js");
const {QWeb, Context} = owl;
const {useState, useContext} = owl.hooks;
/* Hide AppDrawer in desktop and mobile modes.
* To avoid delays in pages with a lot of DOM nodes we make
* sub-groups' with 'querySelector' to improve the performance.
*/
function closeAppDrawer() {
_.defer(function () {
// Need close AppDrawer?
var menu_apps_dropdown = document.querySelector(".o_menu_apps .dropdown");
$(menu_apps_dropdown)
.has(".dropdown-menu.show")
.find("> a")
.dropdown("toggle");
// Need close Sections Menu?
// TODO: Change to 'hide' in modern Bootstrap >4.1
var menu_sections = document.querySelector(
".o_menu_sections li.show .dropdown-toggle"
);
$(menu_sections).dropdown("toggle");
// Need close Mobile?
var menu_sections_mobile = document.querySelector(".o_menu_sections.show");
$(menu_sections_mobile).collapse("hide");
});
}
/**
* 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;
}
if (menu.children.length) {
_.reduce(menu.children, findNames, memo);
}
return memo;
}
AppsMenu.include({
events: _.extend(
{
"keydown .search-input input": "_searchResultsNavigate",
"input .search-input input": "_searchMenusSchedule",
"click .o-menu-search-result": "_searchResultChosen",
"shown.bs.dropdown": "_searchFocus",
"hidden.bs.dropdown": "_searchReset",
"hide.bs.dropdown": "_hideAppsMenu",
},
AppsMenu.prototype.events
),
/**
* Rescue some menu data stripped out in original method.
*
* @override
*/
init: function (parent, menuData) {
this._super.apply(this, arguments);
// Keep base64 icon for main menus
for (const n in this._apps) {
this._apps[n].web_icon_data = menuData.children[n].web_icon_data;
}
// This is handy to be kept
this.menuData = menuData;
// Store menu data in a format searchable by fuzzy.js
this._searchableMenus = _.reduce(menuData.children, findNames, {});
// Search only after timeout, for fast typers
this._search_def = false;
},
/**
* @override
*/
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);
},
/**
* Prevent the menu from being opened twice
*
* @override
*/
_onAppsMenuItemClicked: function (ev) {
this._super.apply(this, arguments);
ev.preventDefault();
ev.stopPropagation();
},
/**
* Get all info for a given menu.
*
* @param {String} key
* Full path to requested menu.
*
* @returns {Object}
* Menu definition, plus extra needed keys.
*/
_menuInfo: function (key) {
const original = this._searchableMenus[key];
return _.extend(
{
action_id: parseInt(original.action.split(",")[1], 10),
},
original
);
},
/**
* Autofocus on search field on big screens.
*/
_searchFocus: function () {
if (!config.device.isMobile) {
// This timeout is necessary since the menu has a 100ms fading animation
setTimeout(() => this.$search_input.focus(), 100);
}
},
/**
* 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 = new Promise((resolve) => {
setTimeout(resolve, 50);
});
this._search_def.then(this._searchMenus.bind(this));
},
/**
* Search among available menu items, and render that search.
*/
_searchMenus: function () {
const 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,
})
);
},
/**
* Filters which menu tree object contains a given subelement
*
* @param {Array|Object} menu containing the tree to search for
* @param {Number} id The id to find
* @returns {Object}
*/
_isInMenuTree: function (menu, id) {
let tree = menu;
if (tree instanceof Object && tree.children) {
tree = tree.children;
}
for (const node of tree) {
if (node.id === id) {
return node;
} else if (node.children.length === 0) {
continue;
} else {
const root = this._isInMenuTree(node.children, id);
if (root) {
return root;
}
}
}
return false;
},
/**
* Use chooses a search result, so we navigate to that menu
*
* @param {jQuery.Event} event
*/
_searchResultChosen: function (event) {
event.preventDefault();
event.stopPropagation();
const $result = $(event.currentTarget);
const data = $result.data();
// 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
const app = this.menuData.children.find((app_menu) => {
if (app_menu.id == data.menuId) {
return true;
}
return this._isInMenuTree(app_menu, data.menuId);
});
if (!app) {
return;
}
// Update navbar menus
core.bus.trigger("change_menu_section", app.id);
},
/**
* Navigate among search results
*
* @param {jQuery.Event} event
*/
_searchResultsNavigate: function (event) {
// Find current results and active element (1st by default)
const all = this.$search_results.find(".o-menu-search-result"),
pre_focused = all.filter(".active") || $(all[0]);
let offset = all.index(pre_focused),
key = event.key;
// Keyboard navigation only supports search results
if (!all.length) {
return;
}
// 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;
default:
// Other keys are useless in this event
return;
}
// Allow looping on results
if (offset < 0) {
offset = all.length + offset;
} else if (offset >= all.length) {
offset -= all.length;
}
// Switch active element
const 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,
},
});
},
/*
* Control if AppDrawer can be closed
*/
_hideAppsMenu: function () {
return !this.$("input").is(":focus");
},
});
BasicController.include({
/**
* Close the AppDrawer if the data set is dirty and a discard dialog
* is opened
*
* @override
*/
canBeDiscarded: function (recordID) {
if (this.model.isDirty(recordID || this.handle)) {
closeAppDrawer();
}
return this._super.apply(this, arguments);
},
});
Menu.include({
events: _.extend(
{
// Clicking a hamburger menu item should close the hamburger
"click .o_menu_sections [role=menuitem]": "_onClickMenuItem",
// 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);
},
/**
* Hide menus for current app if you're in mobile
*/
_hideMobileSubmenus: function () {
if (
config.device.isMobile &&
this.$menu_toggle.is(":visible") &&
this.$section_placeholder.is(":visible")
) {
this.$section_placeholder.collapse("hide");
}
},
/**
* Prevent hide the menu (should be closed when action is loaded)
*
* @param {ClickEvent} ev
*/
_onClickMenuItem: function (ev) {
ev.stopPropagation();
},
/**
* No menu brand in mobiles
*
* @override
*/
_updateMenuBrand: function () {
if (!config.device.isMobile) {
return this._super.apply(this, arguments);
}
},
});
RelationalFields.FieldStatus.include({
/**
* Fold all on mobiles.
*
* @override
*/
_setState: function () {
this._super.apply(this, arguments);
if (config.device.isMobile) {
_.map(this.status_information, (value) => {
value.fold = true;
});
}
},
});
// Sticky Column Selector
ListRenderer.include({
_renderView: function () {
const self = this;
return this._super.apply(this, arguments).then(() => {
const $col_selector = self.$el.find(
".o_optional_columns_dropdown_toggle"
);
if ($col_selector.length !== 0) {
const $th = self.$el.find("thead>tr:first>th:last");
$col_selector.appendTo($th);
}
});
},
_onToggleOptionalColumnDropdown: function (ev) {
// FIXME: For some strange reason the 'stopPropagation' call
// in the main method don't work. Invoking here the same method
// does the expected behavior... O_O!
// This prevents the action of sorting the column from being
// launched.
ev.stopPropagation();
this._super.apply(this, arguments);
},
});
// Responsive view "action" buttons
FormRenderer.include({
/**
* In mobiles, put all statusbar buttons in a dropdown.
*
* @override
*/
_renderHeaderButtons: function () {
const $buttons = this._super.apply(this, arguments);
if (
!config.device.isMobile ||
$buttons.children("button:not(.o_invisible_modifier)").length <= 2
) {
return $buttons;
}
// $buttons must be appended by JS because all events are bound
const $dropdown = $(
core.qweb.render("web_responsive.MenuStatusbarButtons")
);
$buttons.addClass("dropdown-menu").appendTo($dropdown);
return $dropdown;
},
});
CalendarRenderer.include({
_getFullCalendarOptions: function () {
var options = this._super.apply(this, arguments);
if (config.device.isMobile) {
options.views.dayGridMonth.columnHeaderFormat = "ddd";
}
return options;
},
});
// Hide AppDrawer or Menu when the action has been completed
ActionManager.include({
/**
* @override
*/
_appendController: function () {
this._super.apply(this, arguments);
closeAppDrawer();
},
});
/**
* 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 = {
/**
* 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) {
const 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);
// TODO: use default odoo device context when it will be realized
const deviceContext = new Context({
isMobile: config.device.isMobile,
size_class: config.device.size_class,
SIZES: config.device.SIZES,
});
window.addEventListener(
"resize",
owl.utils.debounce(() => {
const state = deviceContext.state;
if (state.isMobile !== config.device.isMobile) {
state.isMobile = !state.isMobile;
}
if (state.size_class !== config.device.size_class) {
state.size_class = config.device.size_class;
}
}, 15)
);
// Patch attachment viewer to add min/max buttons capability
PatchableAttachmentViewer.patch("web_responsive.AttachmentViewer", (T) => {
class AttachmentViewerPatchResponsive extends T {
constructor() {
super(...arguments);
this.state = useState({
maximized: false,
});
}
// Disable auto-close to allow to use form in edit mode.
isCloseable() {
return false;
}
}
return AttachmentViewerPatchResponsive;
});
QWeb.components.AttachmentViewer = PatchableAttachmentViewer;
// Patch control panel to add states for mobile quick search
ControlPanel.patch("web_responsive.ControlPanelMobile", (T) => {
class ControlPanelPatchResponsive extends T {
constructor() {
super(...arguments);
this.state = useState({
mobileSearchMode: "",
});
this.device = useContext(deviceContext);
}
}
return ControlPanelPatchResponsive;
});
// Patch search panel to add functionality for mobile view
SearchPanel.patch("web_responsive.SearchPanelMobile", (T) => {
class SearchPanelPatchResponsive extends T {
constructor() {
super(...arguments);
this.state.mobileSearch = false;
this.device = useContext(deviceContext);
}
getActiveSummary() {
const selection = [];
for (const filter of this.model.get("sections")) {
let filterValues = [];
if (filter.type === "category") {
if (filter.activeValueId) {
const parentIds = this._getAncestorValueIds(
filter,
filter.activeValueId
);
filterValues = [...parentIds, filter.activeValueId].map(
(valueId) => filter.values.get(valueId).display_name
);
}
} else {
let values = [];
if (filter.groups) {
values = Array.from(
filter.groups.values(),
(g) => g.values
).flat();
}
if (filter.values) {
values = [...filter.values.values()];
}
filterValues = values
.filter((v) => v.checked)
.map((v) => v.display_name);
}
if (filterValues.length) {
selection.push({
values: filterValues,
icon: filter.icon,
color: filter.color,
type: filter.type,
});
}
}
return selection;
}
}
return SearchPanelPatchResponsive;
});
return {
deviceContext: deviceContext,
};
});

View File

@ -0,0 +1,97 @@
@include media-breakpoint-down(sm) {
.o_kanban_view.o_kanban_grouped {
display: block;
position: relative;
overflow-x: hidden;
&.o_renderer_with_searchpanel {
width: 100%;
}
.o_kanban_mobile_tabs_container {
position: sticky;
display: flex;
justify-content: space-between;
width: 100%;
top: 0;
z-index: 1;
background-color: #5e5e5e;
.o_kanban_mobile_add_column {
height: $o-kanban-mobile-tabs-height;
padding: 10px;
border-left: grey 1px solid;
color: white;
font-size: 14px;
}
.o_kanban_mobile_tabs {
position: relative;
display: flex;
width: 100%;
height: $o-kanban-mobile-tabs-height;
overflow-x: auto;
.o_kanban_mobile_tab {
height: $o-kanban-mobile-tabs-height;
padding: 10px 20px;
font-size: 14px;
color: white;
&.o_current {
font-weight: bold;
border-bottom: 3px solid $o-brand-primary;
}
.o_column_title {
white-space: nowrap;
text-transform: uppercase;
}
}
}
}
.o_kanban_columns_content {
position: relative;
}
// [class] to get same specificity as elsewhere (kanban_view.less)
&[class] .o_kanban_group:not(.o_column_folded) {
@include o-position-absolute(
$top: $o-kanban-mobile-tabs-height,
$left: 0,
$bottom: 0
);
width: 100%;
padding: 0;
margin-left: 0; // override the margin-left: -1px of the desktop mode
border: none;
&.o_current {
position: inherit;
top: 0;
&.o_kanban_no_records {
// set a default height for clarity when embedded in another view
min-height: $o-kanban-mobile-empty-height;
}
}
.o_kanban_header {
display: none;
}
.o_kanban_record,
.o_kanban_quick_create {
border: none;
border-bottom: 1px solid lightgray;
padding: 10px 16px;
margin: 0;
}
}
}
.o_kanban_view .o_column_quick_create {
.o_quick_create_folded {
display: none !important;
}
.o_quick_create_unfolded {
width: 100%;
}
}
}

View File

@ -0,0 +1,109 @@
.o_web_client {
.o_mobile_search {
position: fixed;
top: 0;
left: 0;
bottom: 0;
padding: 0;
width: 100%;
background-color: white;
z-index: $zindex-modal;
overflow: auto;
.o_mobile_search_header {
height: 46px;
margin-bottom: 10px;
width: 100%;
background-color: $o-brand-odoo;
color: white;
span:active {
background-color: darken($o-brand-primary, 10%);
}
span {
cursor: pointer;
}
}
.o_searchview_input_container {
display: flex;
padding: 15px 20px 0 20px;
position: relative;
.o_searchview_input {
width: 100%;
margin-bottom: 15px;
border-bottom: 1px solid $o-brand-secondary;
}
.o_searchview_facet {
border-radius: 10px;
display: inline-flex;
order: 1;
.o_searchview_facet_label {
border-radius: 2em 0em 0em 2em;
}
}
.o_searchview_autocomplete {
top: 100%;
> li {
margin: 5px 0px;
}
}
}
.o_mobile_search_filter {
padding-bottom: 15%;
.o_dropdown {
width: 100%;
margin: 15px 5px 0px 5px;
border: solid 1px darken(gray("200"), 20%);
}
.o_dropdown_toggler_btn {
width: 100%;
text-align: left;
&:after {
display: none;
}
}
// We disable the backdrop in this case because it prevents any
// interaction outside of a dropdown while it is open.
.dropdown-backdrop {
z-index: -1;
}
.dropdown-menu {
// Here we use !important because of popper js adding custom style
// to element so to override it use !important
position: relative !important;
width: 100% !important;
transform: translate3d(0, 0, 0) !important;
box-shadow: none;
border: none;
color: gray("600");
.divider {
margin: 0px;
}
> li > a {
padding: 10px 26px;
}
}
}
.o_mobile_search_show_result {
padding: 10px;
font-size: 15px;
}
}
}
// Search panel
@include media-breakpoint-down(sm) {
.o_controller_with_searchpanel {
display: block;
.o_search_panel {
height: auto;
padding: 8px;
border-left: 1px solid $gray-300;
section {
padding: 0px 16px;
}
}
.o_search_panel_summary {
cursor: pointer;
}
}
}

View File

@ -0,0 +1,970 @@
/* Copyright 2018 Tecnativa - Jairo Llopis
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
$chatter_zone_width: 35%;
@mixin full-screen-dropdown {
border: none;
box-shadow: none;
display: flex;
flex-direction: column;
height: calc(100vh - #{$o-navbar-height});
position: fixed;
margin: 0;
width: 100vw;
z-index: 100;
// Inline style will override our `top`, so we need !important here
top: $o-navbar-height !important;
transform: none !important;
}
// Support for long titles
@include media-breakpoint-up(md) {
.o_form_view .oe_button_box + .oe_title,
.o_form_view .oe_button_box + .oe_avatar + .oe_title {
/* Button-box has a hardcoded width of 132px per button and have three columns */
width: calc(100% - 450px);
}
}
// 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: gray("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;
overflow: auto;
.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;
}
}
}
}
.o_main_navbar {
color: color-yiq($o-brand-odoo);
> ul > li > a,
> ul > li > label {
color: color-yiq($o-brand-odoo);
}
.dropdown-menu.show {
max-height: calc(100vh - #{$o-navbar-height});
}
}
// Iconized full screen apps menu
.o_menu_apps {
user-select: none;
a.full {
width: $o-navbar-height;
text-align: center;
}
.dropdown-menu.show {
opacity: 1;
visibility: visible;
}
.dropdown-menu {
@include full-screen-dropdown();
opacity: 0;
visibility: hidden;
transition: visibility 100ms ease, opacity 100ms ease;
background: url("../img/home-menu-bg-overlay.svg"),
linear-gradient(
to bottom,
$o-brand-odoo,
desaturate(lighten($o-brand-odoo, 20%), 15)
);
background-size: cover;
border-radius: 0;
// 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: calc((100vw - 850px) / 2);
right: calc((100vw - 850px) / 2);
}
}
.o_app {
align-items: center;
display: flex;
padding: 10px 0;
border-radius: 4px;
flex-direction: column;
justify-content: flex-start;
background: none;
transition: 300ms ease;
transition-property: background-color;
white-space: normal;
text-align: center;
img {
box-shadow: none;
transition: 300ms ease;
transition-property: box-shadow, transform;
}
&:focus {
background-color: rgba(gray("white"), 0.05);
}
// Size depends on screen
width: 33.33333333%;
@include media-breakpoint-up(sm) {
width: 25%;
}
@include media-breakpoint-up(md) {
width: 16.6666666%;
}
}
.o_app:hover img {
transform: translateY(-3px);
box-shadow: 0 9px 12px -4px rgba(gray("black"), 0.3);
}
// Hide app icons when searching
.has-results ~ .o_app {
display: none;
}
.o-app-icon {
height: auto;
max-width: 6rem;
}
.o-app-name {
color: gray("white");
margin-top: 4px;
font-size: 1.25rem;
text-shadow: 1px 1px 1px rgba(gray("black"), 0.4);
}
// 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;
padding-left: 3rem;
white-space: normal;
}
.search-container {
padding-top: 1rem;
padding-bottom: 1.5rem;
.search-input {
margin-bottom: 0 !important;
padding: 0;
.input-group {
box-shadow: inset 0 1px 0 rgba(gray("white"), 0.1),
0 1px 0 rgba(gray("black"), 0.1);
text-shadow: 0 1px 0 rgba(gray("black"), 0.5);
border-radius: 4px;
padding: 0.4rem 0.8rem;
background-color: rgba(gray("white"), 0.1);
@include media-breakpoint-up(md) {
padding: 0.8rem 1.2rem;
}
.input-group-prepend {
span.fa {
color: gray("white");
font-size: 1.5rem;
margin-right: 1rem;
padding-top: 1px;
}
}
.form-control {
height: 2rem;
background: none;
border: none;
color: gray("white");
display: block;
padding: 1px 2px 2px 2px;
box-shadow: none;
&::placeholder {
color: gray("white");
opacity: 0.5;
}
}
}
}
}
// Allow to scroll only on results, keeping static search box above
.search-container.has-results {
height: 100%;
.search-results {
max-height: calc(100vh - 47px - 6em);
overflow-y: hidden;
overflow-x: scroll;
overflow: auto;
background: url("../img/home-menu-bg-overlay.svg"),
linear-gradient(to bottom, gray("200"), gray("white"));
background-position: center;
background-size: cover;
}
}
}
}
// Scroll all but top bar
html .o_web_client .o_action_manager .o_action {
@include media-breakpoint-down(sm) {
overflow: auto;
.o_content {
overflow: visible;
}
}
max-width: 100%;
}
// Make enough space for search panel filters buttons
.o_control_panel {
// There is no media breakpoint for XL upper bound
@include media-breakpoint-up(lg) {
@media (max-width: 1360px) {
.o_cp_top_left,
.o_cp_bottom_left {
width: 40%;
}
.o_cp_top_right,
.o_cp_bottom_right {
width: 60%;
}
}
}
// For FULL HD devices
@media (min-width: 1900px) {
.o_cp_top_left,
.o_cp_bottom_left {
width: 60%;
}
.o_cp_top_right,
.o_cp_bottom_right {
width: 40%;
}
}
@include media-breakpoint-only(md) {
.o_search_options_hide_labels .o_dropdown_title {
display: none;
}
}
.o_cp_bottom_right {
height: 30px;
}
}
// Mobile Control panel (breadcrumbs, search box, buttons...)
@include media-breakpoint-down(sm) {
.o_control_panel {
// Avoid horizontal scrolling of control panel.
// It doesn't work on iOS Safari, but it looks similar as
// without this patch. With this patch it looks better for
// other browsers.
position: sticky;
left: 0;
z-index: 3;
// Arrange buttons to use space better
.o_cp_top_left,
.o_cp_top_right {
flex: 1 1 100%;
}
.o_cp_top_left {
flex-basis: 80%;
}
.o_cp_top_right {
flex-basis: 20%;
}
.o_cp_bottom {
position: relative; // Necessary for dropdown menu positioning
display: block;
margin: 0;
}
.o_cp_bottom_left {
float: left;
margin: 5px 0;
}
.o_cp_bottom_right {
float: right;
height: 30px;
padding-left: 10px;
margin: 5px 0;
}
.o_cp_bottom_right,
.o_cp_pager {
white-space: nowrap;
}
.o_cp_pager {
margin-bottom: 0;
}
.o_cp_bottom_left > .o_cp_action_menus {
padding-right: 0;
.o_dropdown_title,
.fa-chevron-right,
.fa-chevron-down {
display: none;
}
.o_dropdown_toggler_btn {
margin: 0px 2px;
}
@include media-breakpoint-down(xs) {
.o_dropdown {
position: static;
}
.dropdown-menu {
right: 0;
left: 0;
top: 35px;
}
}
}
// 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: "\f060"; // .fa-arrow-left
cursor: pointer;
font-family: FontAwesome;
}
a {
display: none;
}
}
}
// Ellipsize long breadcrumbs
.breadcrumb {
max-width: 100%;
text-overflow: ellipsis;
}
// 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;
}
.o_searchview {
padding: 1px 0px 3px 0px;
&.o_searchview_mobile {
cursor: pointer;
}
&.o_searchview_quick {
display: flex;
flex: 1 1 auto;
align-items: center;
.o_searchview_input_container {
flex: 1 1 auto;
}
}
}
}
.o_calendar_view .o_calendar_widget {
.fc-timeGridDay-view .fc-axis,
.fc-timeGridWeek-view .fc-axis {
padding-left: 0px;
}
.fc-dayGridMonth-view {
padding-left: 0px;
.fc-week-number {
display: none;
}
}
.fc-dayGridYear-view {
padding-left: 0px;
> .fc-month-container > .fc-month {
width: 100%;
}
}
.fc-timeGridDay-view .fc-widget-header {
margin: 0 4px;
}
.fc-timeGridWeek-view .fc-widget-header {
word-spacing: 4em;
white-space: normal;
text-align: center;
}
}
.o_base_settings .o_setting_container {
display: block;
.settings_tab {
flex-flow: row nowrap;
padding-top: 0px;
.tab {
padding-right: 16px;
}
.selected {
background-color: #212529;
box-shadow: inset 0 -5px #7c7bad;
}
}
}
}
// Size of labels
.o_web_client {
&.o_chatter_position_sided {
.o_action_manager {
.o_content,
.modal-content {
@include media-breakpoint-up(xl, $o-extra-grid-breakpoints) {
.o_inner_group {
.o_td_label {
min-width: 260px !important;
}
}
}
@include media-breakpoint-between(lg, xl, $o-extra-grid-breakpoints) {
.o_group_col_6 {
width: 100% !important;
}
}
}
}
}
&:not(.o_chatter_position_sided) {
@include media-breakpoint-up(lg, $o-extra-grid-breakpoints) {
.o_action_manager {
.o_content,
.modal-content {
.o_inner_group {
.o_td_label {
min-width: 260px !important;
}
}
}
}
}
}
}
// Normal views
.o_content,
.modal-content {
max-width: 100%;
// Form views
.o_form_view {
.o_form_sheet {
max-width: calc(100% - 32px);
overflow-x: auto;
}
.o_FormRenderer_chatterContainer {
padding-top: 0;
.o_Activity_info {
flex-wrap: wrap;
}
.o_ActivityBox_title {
margin-bottom: 0;
}
.o_MessageList_separatorDate {
padding-bottom: 0;
}
}
// Sided chatter scrolling behavior
.o_Chatter {
height: fit-content;
.o_Chatter_fixedPanel {
position: sticky;
top: 0;
z-index: 1;
background-color: white;
padding-bottom: 10px;
}
.o_Chatter_scrollPanel {
overflow: initial;
}
}
// Sticky statusbar
.o_form_statusbar {
position: sticky;
top: 0;
z-index: 2;
}
// Support for long title (with ellipsis)
.oe_title {
span.o_field_widget:not(.oe_inline) {
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: initial;
&:active {
white-space: normal;
}
}
}
@include media-breakpoint-down(sm) {
min-width: auto;
// More buttons border
.oe_button_box {
.o_dropdown_more {
button:last-child {
border-right: 1px solid gray("400");
}
}
}
// Avoid overflow on forms with title and/or button box
.oe_title {
max-width: 100%;
}
.oe_button_box + .oe_title,
.oe_button_box + .oe_avatar + .oe_title {
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.dropdown-menu > .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,
.o_FormRenderer_chatterContainer {
min-width: auto;
max-width: 98%;
}
// Settings pages
.app_settings_block {
.row {
margin: 0;
}
}
.o_FormRenderer_chatterContainer {
padding-top: initial;
// Display send button on small screens
.o_thread_composer {
padding-left: $o-mail-thread-avatar-size * 0.5;
.o_composer_button_send {
display: initial !important; // Forced in core
}
.o_chatter_avatar {
display: none;
}
}
.o_chatter_topbar {
> .o_topbar_right_area {
flex: 1 0 auto;
flex-wrap: wrap;
max-width: 100%;
// Display followers on small screens
.o_followers {
display: initial !important; // Forced in core
@include media-breakpoint-down(xs) {
padding-bottom: 50px;
}
}
}
}
}
}
}
//No content message improvements on mobile
@include media-breakpoint-down(md) {
.o_view_nocontent {
top: 80px;
}
.o_nocontent_help {
box-shadow: none;
}
.o_sample_data_disabled {
display: none;
}
}
// Sided chatter, if user wants
.o_chatter_position_sided & {
@include media-breakpoint-up(lg) {
.o_form_view:not(.o_form_nosheet) {
display: flex;
flex-flow: row nowrap;
height: 100%;
.o_form_sheet_bg {
flex: 1 1 auto;
overflow: auto;
> .o_form_sheet {
min-width: unset;
}
}
.o_FormRenderer_chatterContainer {
border-left: 1px solid gray("400");
flex: 0 0 $chatter_zone_width;
max-width: initial;
min-width: initial;
overflow: auto;
.o_chatter_header_container {
padding-top: $grid-gutter-width * 0.5;
top: 0;
position: sticky;
background-color: $o-view-background-color;
z-index: 1;
// Scrollable input text to avoid hide conversation & buttons
.o_composer_text_field {
max-height: 120px;
overflow-y: auto !important; /* Forced because Odoo uses inline style */
}
.o_attachments_list {
overflow: auto;
max-height: $o-mail-attachment-image-size * 3;
margin-top: 0.4em;
}
.o_attachments_previews {
overflow: auto;
max-height: $o-mail-attachment-image-size * 6;
}
}
}
}
}
}
}
// Sticky Header & Footer in List View
.o_list_view {
.table-responsive {
.o_list_table {
// th & td are here for compatibility with chrome
thead tr:nth-child(1) th {
position: sticky;
top: 0;
z-index: 1;
}
thead tr:nth-child(1) th {
background-color: $o-list-footer-bg-color;
}
tfoot,
tfoot tr:nth-child(1) td {
position: sticky;
bottom: 0;
}
tfoot tr:nth-child(1) td {
background-color: $o-list-footer-bg-color;
}
}
}
}
// Big checkboxes
.o_list_view {
.custom-checkbox:not(.o_boolean_toggle) {
margin-right: 10px;
.custom-control-label {
top: -6px;
&::after {
width: 24px;
height: 24px;
}
&::before {
outline: none !important;
border: 1px solid #4c4c4c;
width: 24px;
height: 24px;
}
}
}
}
// Waiting Cursor
.oe_wait {
cursor: progress;
}
// Attachment Viewer
.o_web_client.o_chatter_position_sided .o_Dialog_AttachmentViewer {
/* Show sided viewer on large screens */
@include media-breakpoint-up(lg) {
position: static;
.o_AttachmentViewer_main {
padding-bottom: 20px;
}
.o_AttachmentViewer {
// On-top of navbar
z-index: 10;
position: absolute;
right: 0;
top: 0;
bottom: 0;
margin-left: auto;
background-color: rgba(0, 0, 0, 0.7);
.o_AttachmentViewer_name {
display: contents;
}
width: $chatter_zone_width;
&.o_AttachmentViewer_maximized {
width: 100%;
}
/* Show/Hide control buttons (next, prev, etc..) */
&:hover .o_AttachmentViewer_buttonNavigation,
&:hover .o_AttachmentViewer_toolbar {
display: flex;
}
.o_AttachmentViewer_buttonNavigation,
.o_AttachmentViewer_toolbar {
display: none;
}
.o_AttachmentViewer_viewIframe {
width: 95%;
}
}
}
@include media-breakpoint-down(md) {
.o_AttachmentViewer_headerItemButtonMinimize,
.o_AttachmentViewer_headerItemButtonMaximize {
display: none;
}
}
}
/* Attachment Viewer Max/Min buttons only are useful in sided mode */
.o_web_client:not(.o_chatter_position_sided) {
.o_AttachmentViewer_headerItemButtonMinimize,
.o_AttachmentViewer_headerItemButtonMaximize {
display: none;
}
}
.o_control_panel {
// Filter Menu
// Cut long filters names in the filters menu
.o_filter_menu {
.o_menu_item {
width: auto;
@include media-breakpoint-up(md) {
max-width: 250px;
}
a {
overflow: hidden;
text-overflow: ellipsis;
}
}
}
// Enable scroll on dropdowns
.o_cp_buttons .dropdown-menu {
max-height: 70vh;
overflow-y: auto;
overflow-x: hidden;
}
// 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;
}
font-size: 1.3em;
}
}
}
}
// Shortcut table ui improvement
.o_shortcut_table {
width: 100%;
}

View File

@ -0,0 +1,69 @@
<?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 must be clickable -->
<t t-jquery=".o_app" t-operation="attributes">
<attribute
name="t-attf-href"
t-translation="off"
>#menu_id=#{app.menuID}&amp;action_id=#{app.actionID}</attribute>
<attribute name="draggable" t-translation="off">false</attribute>
</t>
<!-- App icons should be more than a text -->
<t t-jquery=".o_app &gt; 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 align-items-center col-12">
<div class="search-input">
<div class="input-group">
<div class="input-group-prepend">
<span class="fa fa-search" />
</div>
<input
type="search"
autocomplete="off"
placeholder="Search menus..."
class="form-control"
/>
</div>
<div class="search-results ml-auto mr-auto rounded" />
</div>
</div>
</t>
</t>
<!-- Separate app icon template, for easier inheritance -->
<t t-name="web_responsive.AppIcon">
<img
class="o-app-icon"
draggable="false"
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)" />
<a
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-attf-href="#menu_id=#{menu.id}&amp;action_id=#{menu.action_id}"
t-att-data-menu-id="menu.id"
t-att-data-action-id="menu.action_id"
t-att-data-parent-id="menu.parent_id[0]"
draggable="false"
t-raw="result.string"
/>
</t>
</t>
</template>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2019 Tecnativa - Alexandre Díaz
Copyright 2021 Sergey Shebanin
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<template>
<t t-inherit="mail.Dialog" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('o_Dialog')]" position="attributes">
<attribute
name="t-attf-class"
t-translation="off"
>o_Dialog_{{dialog.record['constructor'].name}}</attribute>
</xpath>
</t>
<t t-inherit="mail.AttachmentViewer" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('o_AttachmentViewer')]" position="attributes">
<attribute
name="t-att-class"
t-translation="off"
>state.maximized ? 'o_AttachmentViewer_maximized' : ''</attribute>
</xpath>
<xpath
expr="//div[hasclass('o_AttachmentViewer_header')]/div[hasclass('o-autogrow')]"
position="after"
>
<div
t-if="!state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMaximize"
t-on-click="state.maximized=true"
role="button"
title="Maximize"
aria-label="Maximize"
>
<i class="fa fa-fw fa-window-maximize" role="img" />
</div>
<div
t-if="state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMinimize"
t-on-click="state.maximized=false"
role="button"
title="Minimize"
aria-label="Minimize"
>
<i class="fa fa-fw fa-window-minimize" role="img" />
</div>
</xpath>
</t>
</template>

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2021 Sergey Shebanin
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<t t-inherit="web.ControlPanel" t-inherit-mode="extension" owl="1">
<xpath expr="//nav[hasclass('o_cp_switch_buttons')]" position="replace">
<nav
t-if="props.views.length gt 1"
class="btn-group o_cp_switch_buttons"
role="toolbar"
aria-label="View switcher"
>
<t
t-set="collapse_switchview"
t-value="device.size_class &lt;= device.SIZES.LG"
/>
<button
t-if="collapse_switchview"
class="btn btn-link btn-sm"
data-toggle="dropdown"
aria-expanded="false"
>
<span
t-attf-class="fa fa-lg o_switch_view o_{{ env.view.type }} {{ props.views.filter(view => view.type === env.view.type)[0].icon }}"
/>
</button>
<ul
t-if="collapse_switchview"
class="dropdown-menu dropdown-menu-right list-inline"
>
<li t-foreach="props.views" t-as="view" t-key="view.type">
<t t-call="web.ViewSwitcherButton" />
</li>
</ul>
<t
t-if="!collapse_switchview"
t-foreach="props.views"
t-as="view"
t-key="view.type"
>
<t t-call="web.ViewSwitcherButton" />
</t>
</nav>
</xpath>
<xpath expr="//div[hasclass('o_searchview')]" position="replace">
<div
t-if="props.withSearchBar"
class="o_searchview"
t-att-class="state.mobileSearchMode == 'quick' ? 'o_searchview_quick' : 'o_searchview_mobile'"
role="search"
aria-autocomplete="list"
t-on-click.self="state.mobileSearchMode = device.isMobile ? 'quick' : ''"
>
<t t-if="!device.isMobile">
<i
class="o_searchview_icon fa fa-search"
title="Search..."
role="img"
aria-label="Search..."
/>
<SearchBar fields="fields" />
</t>
<t t-if="device.isMobile and state.mobileSearchMode == 'quick'">
<button
class="btn btn-link fa fa-arrow-left"
t-on-click.stop="state.mobileSearchMode = ''"
/>
<SearchBar fields="fields" />
<button
class="btn fa fa-filter"
t-on-click.stop="state.mobileSearchMode = 'full'"
/>
</t>
<t
t-if="device.isMobile and state.mobileSearchMode == 'full'"
t-call="web_responsive.MobileSearchView"
/>
<t t-if="device.isMobile and state.mobileSearchMode == ''">
<button
class="btn btn-link fa fa-search"
t-on-click.stop="state.mobileSearchMode = 'quick'"
/>
</t>
</div>
</xpath>
<xpath expr="//div[hasclass('o_cp_top_left')]" position="attributes">
<attribute
name="t-att-class"
t-translation="off"
>device.isMobile and state.mobileSearchMode == 'quick' ? 'o_hidden' : ''</attribute>
</xpath>
<xpath expr="//div[hasclass('o_search_options')]" position="attributes">
<attribute name="t-if" t-translation="off">!device.isMobile</attribute>
<attribute
name="t-att-class"
t-translation="off"
>device.size_class == device.SIZES.MD ? 'o_search_options_hide_labels' : ''</attribute>
</xpath>
</t>
<t t-name="web_responsive.MobileSearchView" owl="1">
<div class="o_mobile_search">
<div class="o_mobile_search_header">
<span
class="o_mobile_search_close float-left mt16 mb16 mr8 ml16"
t-on-click.stop="state.mobileSearchMode = 'quick'"
>
<i class="fa fa-arrow-left" />
<strong class="float-right ml8">FILTER</strong>
</span>
<span
class="float-right o_mobile_search_clear_facets mt16 mr16"
t-on-click.stop="model.dispatch('clearQuery')"
>
<t>CLEAR</t>
</span>
</div>
<SearchBar fields="fields" />
<div class="o_mobile_search_filter o_search_options mb8 mt8 ml16 mr16">
<FilterMenu
t-if="props.searchMenuTypes.includes('filter')"
class="o_filter_menu"
fields="fields"
/>
<GroupByMenu
t-if="props.searchMenuTypes.includes('groupBy')"
class="o_group_by_menu"
fields="fields"
/>
<ComparisonMenu
t-if="props.searchMenuTypes.includes('comparison') and model.get('filters', f => f.type === 'comparison').length"
class="o_comparison_menu"
/>
<FavoriteMenu
t-if="props.searchMenuTypes.includes('favorite')"
class="o_favorite_menu"
/>
</div>
<div
class="btn btn-primary o_mobile_search_show_result fixed-bottom"
t-on-click.stop="state.mobileSearchMode = ''"
>
<t>SEE RESULT</t>
</div>
</div>
</t>
</templates>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2019 Tecnativa - Alexandre Díaz
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<template>
<t t-extend="mail.discuss.ControlButtons">
<t
t-jquery=".o_mail_discuss_button_multi_user_channel"
t-operation="attributes"
>
<attribute name="class" t-translation="off">
btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block
d-none
</attribute>
</t>
</t>
</template>

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
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">
<!-- 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="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="CalendarView.navigation_buttons">
<!-- Add responsive icons to buttons -->
<t t-jquery=".o_calendar_button_today" t-operation="inner">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'calendar-check-o'" />
<t t-set="label">Today</t>
</t>
</t>
</t>
</templates>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<div t-extend="UserMenu.shortcuts">
<t
t-jquery="table.o_shortcut_table > tbody > tr > td:nth-child(2) > span:first-child"
t-operation="after"
>
+ <span class="o_key">Shift</span>
</t>
</div>
</templates>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2017-2018 Tecnativa - Jairo Llopis
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<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>
</templates>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2021 Sergey Shebanin
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<t t-inherit="web.SearchPanel" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('o_search_panel')]" position="inside">
<div
t-if="device.isMobile"
class="o_search_panel_summary"
t-on-click.stop="state.mobileSearch = true"
>
<div class="d-flex flex-wrap align-items-center">
<i class="fa fa-fw fa-filter mr-1" />
<t t-set="filters" t-value="getActiveSummary()" />
<span t-foreach="filters" t-as="filter" class="mx-1">
<i
t-if="filter.icon"
t-attf-class="fa {{ filter.icon }} mr-2"
t-att-style="filter.color and ('color: ' + filter.color)"
/>
<t
t-esc="filter.values.join(filter.type == 'category' ? ' / ' : ', ')"
/>
</span>
<t t-if="!filters.length">All</t>
</div>
</div>
<div
class="o_search_panel_content"
t-att-class="device.isMobile ? (state.mobileSearch ? 'o_mobile_search' : 'd-none'): ''"
/>
</xpath>
<xpath expr="//div[hasclass('o_search_panel_content')]" position="inside">
<div t-if="device.isMobile" class="o_mobile_search_header">
<span
class="o_mobile_search_close float-left mt16 mb16 mr8 ml16"
t-on-click.stop="state.mobileSearch = false"
>
<i class="fa fa-arrow-left" />
<strong class="float-right ml8">FILTER</strong>
</span>
</div>
<xpath expr="//section" position="move" />
<div
t-if="device.isMobile"
class="btn btn-primary o_mobile_search_show_result fixed-bottom"
t-on-click.stop="state.mobileSearch = false"
>
<t>SEE RESULT</t>
</div>
</xpath>
</t>
</templates>

View File

@ -0,0 +1 @@
from . import test_res_users

View File

@ -0,0 +1,13 @@
# Copyright 2018 Alexandre Díaz
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo.tests import common
class TestResUsers(common.TransactionCase):
def test_chatter_position_wr(self):
user_public = self.env.ref("base.public_user")
self.assertEqual(user_public.chatter_position, "sided")
user_public.with_user(user_public).write({"chatter_position": "normal"})
self.assertEqual(user_public.chatter_position, "normal")

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2016 LasLabs Inc.
@author Dave Lasley <dave@laslabs.com>
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<template
id="assets_backend"
name="Open Mobile Assets"
inherit_id="web.assets_backend"
>
<xpath expr="//link[last()]" position="after">
<link
type="text/css"
rel="stylesheet"
href="/web_responsive/static/src/scss/web_responsive.scss"
/>
<link
type="text/css"
rel="stylesheet"
href="/web_responsive/static/src/scss/search_view_mobile.scss"
/>
<link
type="text/css"
rel="stylesheet"
href="/web_responsive/static/src/scss/kanban_view_mobile.scss"
/>
</xpath>
<xpath expr="//script[last()]" position="after">
<script
type="application/javascript"
src="/web_responsive/static/src/js/web_responsive.js"
/>
<script
type="application/javascript"
src="/web_responsive/static/src/js/kanban_renderer_mobile.js"
/>
</xpath>
</template>
</odoo>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" ?>
<!--
Copyright 2018
@author Alexanre Díaz <dev@redneboa.es>
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="view_users_form_simple_modif" model="ir.ui.view">
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form_simple_modif" />
<field name="arch" type="xml">
<xpath expr="//field[@name='email']" position="after">
<field name="chatter_position" readonly="0" />
</xpath>
</field>
</record>
<record id="view_users_form_chatter_pos" model="ir.ui.view">
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='action_id']" position="after">
<field name="chatter_position" readonly="0" />
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018 Alexandre Díaz
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<template
id="webclient_bootstrap"
inherit_id="web.webclient_bootstrap"
name="App Drawer - Web Client"
>
<xpath expr="//t[@t-set='body_classname']" position="attributes">
<attribute
name="t-value"
add="+ ' o_chatter_position_' + (request.env.user.chatter_position or 'normal')"
separator=" "
/>
</xpath>
</template>
<template
id="responsive_web_layout"
inherit_id="web.layout"
name="Responsive Layout"
>
<xpath expr="//meta[last()]" position="after">
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
</xpath>
</template>
</odoo>

View File

@ -0,0 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controller
from . import models
from . import tools
from . import wizard

View File

@ -0,0 +1,53 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Odoo WhatsApp Integration',
'category': 'WhatsApp',
'summary': 'Integrates Odoo with WhatsApp to use WhatsApp messaging service',
'version': '1.0',
'description': """This module integrates Odoo with WhatsApp to use WhatsApp messaging service""",
'depends': ['mail', 'phone_validation'],
'data': [
'data/ir_actions_server_data.xml',
'data/ir_cron_data.xml',
'data/ir_module_category_data.xml',
'data/whatsapp_templates_preview.xml',
'security/res_groups.xml',
'security/ir_rules.xml',
'security/ir.model.access.csv',
'wizard/whatsapp_preview_views.xml',
'wizard/whatsapp_composer_views.xml',
'views/discuss_channel_views.xml',
'views/whatsapp_account_views.xml',
'views/whatsapp_message_views.xml',
'views/whatsapp_template_views.xml',
'views/whatsapp_template_button_views.xml',
'views/whatsapp_template_variable_views.xml',
'views/res_config_settings_views.xml',
'views/whatsapp_menus.xml',
'views/resources.xml',
],
'external_dependencies': {
'python': ['phonenumbers'],
},
# 'assets': {
# 'web.assets_backend': [
# 'whatsapp/static/src/**/*',
# # Don't include dark mode files in light mode
# ('remove', 'whatsapp/static/src/**/*.dark.scss'),
# ],
# "web.assets_web_dark": [
# 'whatsapp/static/src/**/*.dark.scss',
# ],
# 'web.tests_assets': [
# 'whatsapp/static/tests/helpers/**/*.js',
# ],
# 'web.qunit_suite_tests': [
# 'whatsapp/static/tests/**/*',
# ('remove', 'whatsapp/static/tests/helpers/**/*.js'),
# ],
# },
'license': 'OEEL-1',
'application': True,
'installable': True,
}

View File

@ -0,0 +1,757 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import configparser as ConfigParser
import errno
import logging
import optparse
import glob
import os
import sys
import tempfile
import warnings
import odoo
from os.path import expandvars, expanduser, abspath, realpath, normcase
from odoo import release, conf, loglevels
from odoo.tools import appdirs
# todo from odoo.tools
from passlib.context import CryptContext
crypt_context = CryptContext(schemes=['pbkdf2_sha512', 'plaintext'],
deprecated=['plaintext'],
pbkdf2_sha512__rounds=600_000)
class MyOption (optparse.Option, object):
""" optparse Option with two additional attributes.
The list of command line options (getopt.Option) is used to create the
list of the configuration file options. When reading the file, and then
reading the command line arguments, we don't want optparse.parse results
to override the configuration file values. But if we provide default
values to optparse, optparse will return them and we can't know if they
were really provided by the user or not. A solution is to not use
optparse's default attribute, but use a custom one (that will be copied
to create the default values of the configuration file).
"""
def __init__(self, *opts, **attrs):
self.my_default = attrs.pop('my_default', None)
super(MyOption, self).__init__(*opts, **attrs)
DEFAULT_LOG_HANDLER = ':INFO'
def _get_default_datadir():
home = os.path.expanduser('~')
if os.path.isdir(home):
func = appdirs.user_data_dir
else:
if sys.platform in ['win32', 'darwin']:
func = appdirs.site_data_dir
else:
func = lambda **kwarg: "/var/lib/%s" % kwarg['appname'].lower()
# No "version" kwarg as session and filestore paths are shared against series
return func(appname=release.product_name, appauthor=release.author)
def _deduplicate_loggers(loggers):
""" Avoid saving multiple logging levels for the same loggers to a save
file, that just takes space and the list can potentially grow unbounded
if for some odd reason people use :option`--save`` all the time.
"""
# dict(iterable) -> the last item of iterable for any given key wins,
# which is what we want and expect. Output order should not matter as
# there are no duplicates within the output sequence
return (
'{}:{}'.format(logger, level)
for logger, level in dict(it.split(':') for it in loggers).items()
)
class configmanager(object):
def __init__(self, fname=None):
"""Constructor.
:param fname: a shortcut allowing to instantiate :class:`configmanager`
from Python code without resorting to environment
variable
"""
# Options not exposed on the command line. Command line options will be added
# from optparse's parser.
self.options = {
'admin_passwd': 'admin',
'csv_internal_sep': ',',
'publisher_warranty_url': 'http://services.openerp.com/publisher-warranty/',
'reportgz': False,
'root_path': None,
'websocket_keep_alive_timeout': 3600,
'websocket_rate_limit_burst': 10,
'websocket_rate_limit_delay': 0.2,
}
# Not exposed in the configuration file.
self.blacklist_for_save = set([
'publisher_warranty_url', 'load_language', 'root_path',
'init', 'save', 'config', 'update', 'stop_after_init', 'dev_mode', 'shell_interface',
'longpolling_port',
])
# dictionary mapping option destination (keys in self.options) to MyOptions.
self.casts = {}
self.misc = {}
self.config_file = fname
self._LOGLEVELS = dict([
(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')
])
version = "%s %s" % (release.description, release.version)
self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
# Server startup config
group = optparse.OptionGroup(parser, "Common options")
group.add_option("-c", "--config", dest="config", help="specify alternate config file")
group.add_option("-s", "--save", action="store_true", dest="save", default=False,
help="save configuration to ~/.odoorc (or to ~/.openerp_serverrc if it exists)")
group.add_option("-i", "--init", dest="init", help="install one or more modules (comma-separated list, use \"all\" for all modules), requires -d")
group.add_option("-u", "--update", dest="update",
help="update one or more modules (comma-separated list, use \"all\" for all modules). Requires -d.")
group.add_option("--without-demo", dest="without_demo",
help="disable loading demo data for modules to be installed (comma-separated, use \"all\" for all modules). Requires -d and -i. Default is %default",
my_default=False)
group.add_option("-P", "--import-partial", dest="import_partial", my_default='',
help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.")
group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
group.add_option("--addons-path", dest="addons_path",
help="specify additional addons paths (separated by commas).",
action="callback", callback=self._check_addons_path, nargs=1, type="string")
group.add_option("--upgrade-path", dest="upgrade_path",
help="specify an additional upgrade path.",
action="callback", callback=self._check_upgrade_path, nargs=1, type="string")
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules.", my_default='base,web')
group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(),
help="Directory where to store Odoo data")
parser.add_option_group(group)
# HTTP
group = optparse.OptionGroup(parser, "HTTP Service Configuration")
group.add_option("--http-interface", dest="http_interface", my_default='',
help="Listen interface address for HTTP services. "
"Keep empty to listen on all interfaces (0.0.0.0)")
group.add_option("-p", "--http-port", dest="http_port", my_default=8069,
help="Listen port for the main HTTP service", type="int", metavar="PORT")
group.add_option("--longpolling-port", dest="longpolling_port", my_default=0,
help="Deprecated alias to the gevent-port option", type="int", metavar="PORT")
group.add_option("--gevent-port", dest="gevent_port", my_default=8072,
help="Listen port for the gevent worker", type="int", metavar="PORT")
group.add_option("--no-http", dest="http_enable", action="store_false", my_default=True,
help="Disable the HTTP and Longpolling services entirely")
group.add_option("--proxy-mode", dest="proxy_mode", action="store_true", my_default=False,
help="Activate reverse proxy WSGI wrappers (headers rewriting) "
"Only enable this when running behind a trusted web proxy!")
group.add_option("--x-sendfile", dest="x_sendfile", action="store_true", my_default=False,
help="Activate X-Sendfile (apache) and X-Accel-Redirect (nginx) "
"HTTP response header to delegate the delivery of large "
"files (assets/attachments) to the web server.")
# HTTP: hidden backwards-compatibility for "*xmlrpc*" options
hidden = optparse.SUPPRESS_HELP
group.add_option("--xmlrpc-interface", dest="http_interface", help=hidden)
group.add_option("--xmlrpc-port", dest="http_port", type="int", help=hidden)
group.add_option("--no-xmlrpc", dest="http_enable", action="store_false", help=hidden)
parser.add_option_group(group)
# WEB
group = optparse.OptionGroup(parser, "Web interface Configuration")
group.add_option("--db-filter", dest="dbfilter", my_default='', metavar="REGEXP",
help="Regular expressions for filtering available databases for Web UI. "
"The expression can use %d (domain) and %h (host) placeholders.")
parser.add_option_group(group)
# Testing Group
group = optparse.OptionGroup(parser, "Testing Configuration")
group.add_option("--test-file", dest="test_file", my_default=False,
help="Launch a python test file.")
group.add_option("--test-enable", action="callback", callback=self._test_enable_callback,
dest='test_enable',
help="Enable unit tests.")
group.add_option("--test-tags", dest="test_tags",
help="Comma-separated list of specs to filter which tests to execute. Enable unit tests if set. "
"A filter spec has the format: [-][tag][/module][:class][.method] "
"The '-' specifies if we want to include or exclude tests matching this spec. "
"The tag will match tags added on a class with a @tagged decorator "
"(all Test classes have 'standard' and 'at_install' tags "
"until explicitly removed, see the decorator documentation). "
"'*' will match all tags. "
"If tag is omitted on include mode, its value is 'standard'. "
"If tag is omitted on exclude mode, its value is '*'. "
"The module, class, and method will respectively match the module name, test class name and test method name. "
"Example: --test-tags :TestClass.test_func,/test_module,external "
"Filtering and executing the tests happens twice: right "
"after each module installation/update and at the end "
"of the modules loading. At each stage tests are filtered "
"by --test-tags specs and additionally by dynamic specs "
"'at_install' and 'post_install' correspondingly.")
group.add_option("--screencasts", dest="screencasts", action="store", my_default=None,
metavar='DIR',
help="Screencasts will go in DIR/{db_name}/screencasts.")
temp_tests_dir = os.path.join(tempfile.gettempdir(), 'odoo_tests')
group.add_option("--screenshots", dest="screenshots", action="store", my_default=temp_tests_dir,
metavar='DIR',
help="Screenshots will go in DIR/{db_name}/screenshots. Defaults to %s." % temp_tests_dir)
parser.add_option_group(group)
# Logging Group
group = optparse.OptionGroup(parser, "Logging Configuration")
group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
group.add_option("--syslog", action="store_true", dest="syslog", my_default=False, help="Send the log to the syslog server")
group.add_option('--log-handler', action="append", default=[], my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "odoo.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
group.add_option('--log-web', action="append_const", dest="log_handler", const="odoo.http:DEBUG", help='shortcut for --log-handler=odoo.http:DEBUG')
group.add_option('--log-sql', action="append_const", dest="log_handler", const="odoo.sql_db:DEBUG", help='shortcut for --log-handler=odoo.sql_db:DEBUG')
group.add_option('--log-db', dest='log_db', help="Logging database", my_default=False)
group.add_option('--log-db-level', dest='log_db_level', my_default='warning', help="Logging database level")
# For backward-compatibility, map the old log levels to something
# quite close.
levels = [
'info', 'debug_rpc', 'warn', 'test', 'critical', 'runbot',
'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'
]
group.add_option('--log-level', dest='log_level', type='choice',
choices=levels, my_default='info',
help='specify the level of the logging. Accepted values: %s.' % (levels,))
parser.add_option_group(group)
# SMTP Group
group = optparse.OptionGroup(parser, "SMTP Configuration")
group.add_option('--email-from', dest='email_from', my_default=False,
help='specify the SMTP email address for sending email')
group.add_option('--from-filter', dest='from_filter', my_default=False,
help='specify for which email address the SMTP configuration can be used')
group.add_option('--smtp', dest='smtp_server', my_default='localhost',
help='specify the SMTP server for sending email')
group.add_option('--smtp-port', dest='smtp_port', my_default=25,
help='specify the SMTP port', type="int")
group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', my_default=False,
help='if passed, SMTP connections will be encrypted with SSL (STARTTLS)')
group.add_option('--smtp-user', dest='smtp_user', my_default=False,
help='specify the SMTP username for sending email')
group.add_option('--smtp-password', dest='smtp_password', my_default=False,
help='specify the SMTP password for sending email')
group.add_option('--smtp-ssl-certificate-filename', dest='smtp_ssl_certificate_filename', my_default=False,
help='specify the SSL certificate used for authentication')
group.add_option('--smtp-ssl-private-key-filename', dest='smtp_ssl_private_key_filename', my_default=False,
help='specify the SSL private key used for authentication')
parser.add_option_group(group)
group = optparse.OptionGroup(parser, "Database related options")
group.add_option("-d", "--database", dest="db_name", my_default=False,
help="specify the database name")
group.add_option("-r", "--db_user", dest="db_user", my_default=False,
help="specify the database user name")
group.add_option("-w", "--db_password", dest="db_password", my_default=False,
help="specify the database password")
group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path")
group.add_option("--db_host", dest="db_host", my_default=False,
help="specify the database host")
group.add_option("--db_port", dest="db_port", my_default=False,
help="specify the database port", type="int")
group.add_option("--db_sslmode", dest="db_sslmode", type="choice", my_default='prefer',
choices=['disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full'],
help="specify the database ssl connection mode (see PostgreSQL documentation)")
group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64,
help="specify the maximum number of physical connections to PostgreSQL")
group.add_option("--db_maxconn_gevent", dest="db_maxconn_gevent", type='int', my_default=False,
help="specify the maximum number of physical connections to PostgreSQL specifically for the gevent worker")
group.add_option("--db-template", dest="db_template", my_default="template0",
help="specify a custom database template to create a new database")
parser.add_option_group(group)
group = optparse.OptionGroup(parser, "Internationalisation options",
"Use these options to translate Odoo to another language. "
"See i18n section of the user manual. Option '-d' is mandatory. "
"Option '-l' is mandatory in case of importation"
)
group.add_option('--load-language', dest="load_language",
help="specifies the languages for the translations you want to be loaded")
group.add_option('-l', "--language", dest="language",
help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
group.add_option("--i18n-export", dest="translate_out",
help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
group.add_option("--i18n-import", dest="translate_in",
help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", my_default=False,
help="overwrites existing translation terms on updating a module or importing a CSV or a PO file.")
group.add_option("--modules", dest="translate_modules",
help="specify modules to export. Use in combination with --i18n-export")
parser.add_option_group(group)
security = optparse.OptionGroup(parser, 'Security-related options')
security.add_option('--no-database-list', action="store_false", dest='list_db', my_default=True,
help="Disable the ability to obtain or view the list of databases. "
"Also disable access to the database manager and selector, "
"so be sure to set a proper --database parameter first")
parser.add_option_group(security)
# Advanced options
group = optparse.OptionGroup(parser, "Advanced options")
group.add_option('--dev', dest='dev_mode', type="string",
help="Enable developer mode. Param: List of options separated by comma. "
"Options : all, reload, qweb, xml")
group.add_option('--shell-interface', dest='shell_interface', type="string",
help="Specify a preferred REPL to use in shell mode. Supported REPLs are: "
"[ipython|ptpython|bpython|python]")
group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
help="stop the server after its initialization")
group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=0,
help="Force a limit on the maximum number of records kept in the virtual "
"osv_memory tables. By default there is no limit.",
type="int")
group.add_option("--transient-age-limit", dest="transient_age_limit", my_default=1.0,
help="Time limit (decimal value in hours) records created with a "
"TransientModel (mostly wizard) are kept in the database. Default to 1 hour.",
type="float")
group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=2,
help="Maximum number of threads processing concurrently cron jobs (default 2).",
type="int")
group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
help="Try to enable the unaccent extension when creating new databases.")
group.add_option("--geoip-city-db", "--geoip-db", dest="geoip_city_db", my_default='/usr/share/GeoIP/GeoLite2-City.mmdb',
help="Absolute path to the GeoIP City database file.")
group.add_option("--geoip-country-db", dest="geoip_country_db", my_default='/usr/share/GeoIP/GeoLite2-Country.mmdb',
help="Absolute path to the GeoIP Country database file.")
parser.add_option_group(group)
if os.name == 'posix':
group = optparse.OptionGroup(parser, "Multiprocessing options")
# TODO sensible default for the three following limits.
group.add_option("--workers", dest="workers", my_default=0,
help="Specify the number of workers, 0 disable prefork mode.",
type="int")
group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=2048 * 1024 * 1024,
help="Maximum allowed virtual memory per worker (in bytes), when reached the worker be "
"reset after the current request (default 2048MiB).",
type="int")
group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=2560 * 1024 * 1024,
help="Maximum allowed virtual memory per worker (in bytes), when reached, any memory "
"allocation will fail (default 2560MiB).",
type="int")
group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
help="Maximum allowed CPU time per request (default 60).",
type="int")
group.add_option("--limit-time-real", dest="limit_time_real", my_default=120,
help="Maximum allowed Real time per request (default 120).",
type="int")
group.add_option("--limit-time-real-cron", dest="limit_time_real_cron", my_default=-1,
help="Maximum allowed Real time per cron job. (default: --limit-time-real). "
"Set to 0 for no limit. ",
type="int")
group.add_option("--limit-request", dest="limit_request", my_default=2**16,
help="Maximum number of request to be processed per worker (default 65536).",
type="int")
parser.add_option_group(group)
# Copy all optparse options (i.e. MyOption) into self.options.
for group in parser.option_groups:
for option in group.option_list:
if option.dest not in self.options:
self.options[option.dest] = option.my_default
self.casts[option.dest] = option
# generate default config
self._parse_config()
def parse_config(self, args=None):
""" Parse the configuration file (if any) and the command-line
arguments.
This method initializes odoo.tools.config and openerp.conf (the
former should be removed in the future) with library-wide
configuration values.
This method must be called before proper usage of this library can be
made.
Typical usage of this method:
odoo.tools.config.parse_config(sys.argv[1:])
"""
opt = self._parse_config(args)
odoo.netsvc.init_logger()
self._warn_deprecated_options()
odoo.modules.module.initialize_sys_path()
return opt
def _parse_config(self, args=None):
if args is None:
args = []
opt, args = self.parser.parse_args(args)
def die(cond, msg):
if cond:
self.parser.error(msg)
# Ensures no illegitimate argument is silently discarded (avoids insidious "hyphen to dash" problem)
die(args, "unrecognized parameters: '%s'" % " ".join(args))
die(bool(opt.syslog) and bool(opt.logfile),
"the syslog and logfile options are exclusive")
die(opt.translate_in and (not opt.language or not opt.db_name),
"the i18n-import option cannot be used without the language (-l) and the database (-d) options")
die(opt.overwrite_existing_translations and not (opt.translate_in or opt.update),
"the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
die(opt.translate_out and (not opt.db_name),
"the i18n-export option cannot be used without the database (-d) option")
# Check if the config file exists (-c used, but not -s)
die(not opt.save and opt.config and not os.access(opt.config, os.R_OK),
"The config file '%s' selected with -c/--config doesn't exist or is not readable, "\
"use -s/--save if you want to generate it"% opt.config)
# place/search the config file on Win32 near the server installation
# (../etc from the server)
# if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write,
# else he won't be able to save the configurations, or even to start the server...
# TODO use appdirs
if os.name == 'nt':
rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'odoo.conf')
else:
rcfilepath = os.path.expanduser('~/.odoorc')
old_rcfilepath = os.path.expanduser('~/.openerp_serverrc')
die(os.path.isfile(rcfilepath) and os.path.isfile(old_rcfilepath),
"Found '.odoorc' and '.openerp_serverrc' in your path. Please keep only one of "\
"them, preferably '.odoorc'.")
if not os.path.isfile(rcfilepath) and os.path.isfile(old_rcfilepath):
rcfilepath = old_rcfilepath
self.rcfile = os.path.abspath(
self.config_file or opt.config or os.environ.get('ODOO_RC') or os.environ.get('OPENERP_SERVER') or rcfilepath)
self.load()
# Verify that we want to log or not, if not the output will go to stdout
if self.options['logfile'] in ('None', 'False'):
self.options['logfile'] = False
# the same for the pidfile
if self.options['pidfile'] in ('None', 'False'):
self.options['pidfile'] = False
# the same for the test_tags
if self.options['test_tags'] == 'None':
self.options['test_tags'] = None
# and the server_wide_modules
if self.options['server_wide_modules'] in ('', 'None', 'False'):
self.options['server_wide_modules'] = 'base,web'
# if defined do not take the configfile value even if the defined value is None
keys = ['gevent_port', 'http_interface', 'http_port', 'longpolling_port', 'http_enable', 'x_sendfile',
'db_name', 'db_user', 'db_password', 'db_host', 'db_sslmode',
'db_port', 'db_template', 'logfile', 'pidfile', 'smtp_port',
'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'from_filter',
'smtp_ssl_certificate_filename', 'smtp_ssl_private_key_filename',
'db_maxconn', 'db_maxconn_gevent', 'import_partial', 'addons_path', 'upgrade_path',
'syslog', 'without_demo', 'screencasts', 'screenshots',
'dbfilter', 'log_level', 'log_db',
'log_db_level', 'geoip_city_db', 'geoip_country_db', 'dev_mode',
'shell_interface',
]
for arg in keys:
# Copy the command-line argument (except the special case for log_handler, due to
# action=append requiring a real default, so we cannot use the my_default workaround)
if getattr(opt, arg, None) is not None:
self.options[arg] = getattr(opt, arg)
# ... or keep, but cast, the config file value.
elif isinstance(self.options[arg], str) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
if isinstance(self.options['log_handler'], str):
self.options['log_handler'] = self.options['log_handler'].split(',')
self.options['log_handler'].extend(opt.log_handler)
# if defined but None take the configfile value
keys = [
'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
'dev_mode', 'shell_interface', 'smtp_ssl', 'load_language',
'stop_after_init', 'without_demo', 'http_enable', 'syslog',
'list_db', 'proxy_mode',
'test_file', 'test_tags',
'osv_memory_count_limit', 'transient_age_limit', 'max_cron_threads', 'unaccent',
'data_dir',
'server_wide_modules',
]
posix_keys = [
'workers',
'limit_memory_hard', 'limit_memory_soft',
'limit_time_cpu', 'limit_time_real', 'limit_request', 'limit_time_real_cron'
]
if os.name == 'posix':
keys += posix_keys
else:
self.options.update(dict.fromkeys(posix_keys, None))
# Copy the command-line arguments...
for arg in keys:
if getattr(opt, arg) is not None:
self.options[arg] = getattr(opt, arg)
# ... or keep, but cast, the config file value.
elif isinstance(self.options[arg], str) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
ismultidb = ',' in (self.options.get('db_name') or '')
die(ismultidb and (opt.init or opt.update), "Cannot use -i/--init or -u/--update with multiple databases in the -d/--database/db_name")
self.options['root_path'] = self._normalize(os.path.join(os.path.dirname(__file__), '..'))
if not self.options['addons_path'] or self.options['addons_path']=='None':
default_addons = []
base_addons = os.path.join(self.options['root_path'], 'addons')
if os.path.exists(base_addons):
default_addons.append(base_addons)
main_addons = os.path.abspath(os.path.join(self.options['root_path'], '../addons'))
if os.path.exists(main_addons):
default_addons.append(main_addons)
self.options['addons_path'] = ','.join(default_addons)
else:
self.options['addons_path'] = ",".join(
self._normalize(x)
for x in self.options['addons_path'].split(','))
self.options["upgrade_path"] = (
",".join(self._normalize(x)
for x in self.options['upgrade_path'].split(','))
if self.options['upgrade_path']
else ""
)
self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
self.options['demo'] = (dict(self.options['init'])
if not self.options['without_demo'] else {})
self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
self.options['translate_modules'] = opt.translate_modules and [m.strip() for m in opt.translate_modules.split(',')] or ['all']
self.options['translate_modules'].sort()
dev_split = [s.strip() for s in opt.dev_mode.split(',')] if opt.dev_mode else []
self.options['dev_mode'] = dev_split + (['reload', 'qweb', 'xml'] if 'all' in dev_split else [])
if opt.pg_path:
self.options['pg_path'] = opt.pg_path
self.options['test_enable'] = bool(self.options['test_tags'])
if opt.save:
self.save()
# normalize path options
for key in ['data_dir', 'logfile', 'pidfile', 'test_file', 'screencasts', 'screenshots', 'pg_path', 'translate_out', 'translate_in', 'geoip_city_db', 'geoip_country_db']:
self.options[key] = self._normalize(self.options[key])
conf.addons_paths = self.options['addons_path'].split(',')
conf.server_wide_modules = [
m.strip() for m in self.options['server_wide_modules'].split(',') if m.strip()
]
return opt
def _warn_deprecated_options(self):
if self.options['longpolling_port']:
warnings.warn(
"The longpolling-port is a deprecated alias to "
"the gevent-port option, please use the latter.",
DeprecationWarning)
self.options['gevent_port'] = self.options.pop('longpolling_port')
def _is_addons_path(self, path):
from odoo.modules.module import MANIFEST_NAMES
for f in os.listdir(path):
modpath = os.path.join(path, f)
if os.path.isdir(modpath):
def hasfile(filename):
return os.path.isfile(os.path.join(modpath, filename))
if hasfile('__init__.py') and any(hasfile(mname) for mname in MANIFEST_NAMES):
return True
return False
def _check_addons_path(self, option, opt, value, parser):
ad_paths = []
for path in value.split(','):
path = path.strip()
res = os.path.abspath(os.path.expanduser(path))
if not os.path.isdir(res):
raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, res))
if not self._is_addons_path(res):
raise optparse.OptionValueError("option %s: the path %r is not a valid addons directory" % (opt, path))
ad_paths.append(res)
setattr(parser.values, option.dest, ",".join(ad_paths))
def _check_upgrade_path(self, option, opt, value, parser):
upgrade_path = []
for path in value.split(','):
path = path.strip()
res = self._normalize(path)
if not os.path.isdir(res):
raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
if not self._is_upgrades_path(res):
raise optparse.OptionValueError("option %s: the path %r is not a valid upgrade directory" % (opt, path))
if res not in upgrade_path:
upgrade_path.append(res)
setattr(parser.values, option.dest, ",".join(upgrade_path))
def _is_upgrades_path(self, res):
return any(
glob.glob(os.path.join(res, f"*/*/{prefix}-*.py"))
for prefix in ["pre", "post", "end"]
)
def _test_enable_callback(self, option, opt, value, parser):
if not parser.values.test_tags:
parser.values.test_tags = "+standard"
def load(self):
outdated_options_map = {
'xmlrpc_port': 'http_port',
'xmlrpc_interface': 'http_interface',
'xmlrpc': 'http_enable',
}
p = ConfigParser.RawConfigParser()
try:
p.read([self.rcfile])
for (name,value) in p.items('options'):
name = outdated_options_map.get(name, name)
if value=='True' or value=='true':
value = True
if value=='False' or value=='false':
value = False
self.options[name] = value
#parse the other sections, as well
for sec in p.sections():
if sec == 'options':
continue
self.misc.setdefault(sec, {})
for (name, value) in p.items(sec):
if value=='True' or value=='true':
value = True
if value=='False' or value=='false':
value = False
self.misc[sec][name] = value
except IOError:
pass
except ConfigParser.NoSectionError:
pass
def save(self, keys=None):
p = ConfigParser.RawConfigParser()
loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS))
rc_exists = os.path.exists(self.rcfile)
if rc_exists and keys:
p.read([self.rcfile])
if not p.has_section('options'):
p.add_section('options')
for opt in sorted(self.options):
if keys is not None and opt not in keys:
continue
if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
continue
if opt in self.blacklist_for_save:
continue
if opt in ('log_level',):
p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
elif opt == 'log_handler':
p.set('options', opt, ','.join(_deduplicate_loggers(self.options[opt])))
else:
p.set('options', opt, self.options[opt])
for sec in sorted(self.misc):
p.add_section(sec)
for opt in sorted(self.misc[sec]):
p.set(sec,opt,self.misc[sec][opt])
# try to create the directories and write the file
try:
if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
os.makedirs(os.path.dirname(self.rcfile))
try:
p.write(open(self.rcfile, 'w'))
if not rc_exists:
os.chmod(self.rcfile, 0o600)
except IOError:
sys.stderr.write("ERROR: couldn't write the config file\n")
except OSError:
# what to do if impossible?
sys.stderr.write("ERROR: couldn't create the config directory\n")
def get(self, key, default=None):
return self.options.get(key, default)
def pop(self, key, default=None):
return self.options.pop(key, default)
def get_misc(self, sect, key, default=None):
return self.misc.get(sect,{}).get(key, default)
def __setitem__(self, key, value):
self.options[key] = value
if key in self.options and isinstance(self.options[key], str) and \
key in self.casts and self.casts[key].type in optparse.Option.TYPE_CHECKER:
self.options[key] = optparse.Option.TYPE_CHECKER[self.casts[key].type](self.casts[key], key, self.options[key])
def __getitem__(self, key):
return self.options[key]
@property
def addons_data_dir(self):
add_dir = os.path.join(self['data_dir'], 'addons')
d = os.path.join(add_dir, release.series)
if not os.path.exists(d):
try:
# bootstrap parent dir +rwx
if not os.path.exists(add_dir):
os.makedirs(add_dir, 0o700)
# try to make +rx placeholder dir, will need manual +w to activate it
os.makedirs(d, 0o500)
except OSError:
logging.getLogger(__name__).debug('Failed to create addons data dir %s', d)
return d
@property
def session_dir(self):
d = os.path.join(self['data_dir'], 'sessions')
try:
os.makedirs(d, 0o700)
except OSError as e:
if e.errno != errno.EEXIST:
raise
assert os.access(d, os.W_OK), \
"%s: directory is not writable" % d
return d
def filestore(self, dbname):
return os.path.join(self['data_dir'], 'filestore', dbname)
def set_admin_password(self, new_password):
hash_password = crypt_context.hash if hasattr(crypt_context, 'hash') else crypt_context.encrypt
self.options['admin_passwd'] = hash_password(new_password)
def verify_admin_password(self, password):
"""Verifies the super-admin password, possibly updating the stored hash if needed"""
stored_hash = self.options['admin_passwd']
if not stored_hash:
# empty password/hash => authentication forbidden
return False
result, updated_hash = crypt_context.verify_and_update(password, stored_hash)
if result:
if updated_hash:
self.options['admin_passwd'] = updated_hash
return True
def _normalize(self, path):
if not path:
return ''
return normcase(realpath(abspath(expanduser(expandvars(path.strip())))))
config = configmanager()

View File

@ -0,0 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main
from . import websocket

View File

@ -0,0 +1,138 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import hashlib
import hmac
import json
import logging
from http import HTTPStatus
from markupsafe import Markup
from odoo import http, _
from odoo.http import request
from odoo.tools import consteq
from werkzeug.exceptions import Forbidden
_logger = logging.getLogger(__name__)
class Webhook(http.Controller):
@http.route('/whatsapp/webhook/', methods=['POST'], type="json", auth="public")
def webhookpost(self):
data = json.loads(request.httprequest.data)
print('ddddddddddddd', data)
for entry in data['entry']:
account_id = entry['id']
account = request.env['whatsapp.account'].sudo().search(
[('account_uid', '=', account_id)])
if not self._check_signature(account):
raise Forbidden()
for changes in entry.get('changes', []):
value = changes['value']
phone_number_id = value.get('metadata', {}).get('phone_number_id', {})
if not phone_number_id:
phone_number_id = value.get('whatsapp_business_api_data', {}).get('phone_number_id', {})
if phone_number_id:
wa_account_id = request.env['whatsapp.account'].sudo().search([
('phone_uid', '=', phone_number_id), ('account_uid', '=', account_id)])
if wa_account_id:
# Process Messages and Status webhooks
if changes['field'] == 'messages':
request.env['whatsapp.message']._process_statuses(value)
wa_account_id._process_messages(value)
else:
_logger.warning("There is no phone configured for this whatsapp webhook : %s ", data)
# Process Template webhooks
if value.get('message_template_id'):
# There is no user in webhook, so we need to SUPERUSER_ID to write on template object
template = request.env['whatsapp.template'].sudo().with_context(active_test=False).search(
[('wa_template_uid', '=', value['message_template_id'])])
if template:
if changes['field'] == 'message_template_status_update':
template.write({'status': value['event'].lower()})
if value['event'].lower() == 'rejected':
body = _("Your Template has been rejected.")
description = value.get('other_info', {}).get('description') or value.get('reason')
if description:
body += Markup("<br/>") + _("Reason : %s", description)
template.message_post(body=body)
continue
if changes['field'] == 'message_template_quality_update':
template.write({'quality': value['new_quality_score'].lower()})
continue
if changes['field'] == 'template_category_update':
template.write({'template_type': value['new_category'].lower()})
continue
if changes['field'] == 'messages':
print('messagesmessages')
template_id = self.env['whatsapp.template'].browse(4)
partner_id = self.env['res.partner'].browse(7577)
self.wa_create_composer(template_id, partner_id)
_logger.warning("Unknown Template webhook : %s ", value)
else:
_logger.warning("No Template found for this webhook : %s ", value)
@http.route('/whatsapp/webhook/', methods=['GET'], type="http", auth="public", csrf=False)
def webhookget(self, **kwargs):
"""
This controller is used to verify the webhook.
if challenge is matched then it will make response with challenge.
once it is verified the webhook will be activated.
"""
token = kwargs.get('hub.verify_token')
mode = kwargs.get('hub.mode')
challenge = kwargs.get('hub.challenge')
if not (token and mode and challenge):
return Forbidden()
wa_account = request.env['whatsapp.account'].sudo().search([('webhook_verify_token', '=', token)])
if mode == 'subscribe' and wa_account:
response = request.make_response(challenge)
response.status_code = HTTPStatus.OK
return response
response = request.make_response({})
response.status_code = HTTPStatus.FORBIDDEN
return response
def _check_signature(self, business_account):
"""Whatsapp will sign all requests it makes to our endpoint."""
signature = request.httprequest.headers.get('X-Hub-Signature-256')
if not signature or not signature.startswith('sha256=') or len(signature) != 71:
# Signature must be valid SHA-256 (sha256=<64 hex digits>)
_logger.error('Invalid signature header %r', signature)
return False
if not business_account.app_secret:
_logger.error('App-secret is missing, can not check signature')
return False
expected = hmac.new(
business_account.app_secret.encode(),
msg=request.httprequest.data,
digestmod=hashlib.sha256,
).hexdigest()
return consteq(signature[7:], expected)
def wa_create_composer(self, template_id, records):
values = {
'res_model': records._name,
'res_ids': records.ids,
'wa_template_id': template_id.id,
}
composer_id = self.env['whatsapp.composer'].create(values)
print('composer_idcomposer_id', composer_id)
composer_id.action_send_whatsapp_template()
ddddddddddddd = {'object': 'whatsapp_business_account', 'entry': [{'id': '407473312443001', 'changes': [{'value': {
'messaging_product': 'whatsapp',
'metadata': {'display_phone_number': '15556257124', 'phone_number_id': '329683520237319'},
'contacts': [{'profile': {'name': 'Ahmed Hannachi'}, 'wa_id': '21697527420'}], 'messages': [
{'context': {'from': '15556257124', 'id': 'wamid.HBgLMjE2OTc1Mjc0MjAVAgARGBJDNUQ0MEQyRDQ2N0M5RTNENEQA'},
'from': '21697527420',
'id': 'wamid.HBgLMjE2OTc1Mjc0MjAVAgASGCA4QTJERkI4REY2OTcwM0RCQUVCNjI0QjgyRTlENTg0MgA=',
'timestamp': '1723043846', 'type': 'button',
'button': {'payload': 'Rep 1 pour ce message', 'text': 'Rep 1 pour ce message'}}]},
'field': 'messages'}]}]}

View File

@ -0,0 +1,64 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
from odoo.http import Controller, request, route, SessionExpiredException
from odoo.addons.base.models.assetsbundle import AssetsBundle
from ..models.discuss.bus import channel_with_db
from ..tools.websocket import WebsocketConnectionHandler
# todo from odoo/addons/bus/Controller/websocket.py
class WebsocketController(Controller):
@route('/websocket', type="http", auth="public", cors='*', websocket=True)
def websocket(self):
"""
Handle the websocket handshake, upgrade the connection if
successfull.
"""
return WebsocketConnectionHandler.open_connection(request)
@route('/websocket/health', type='http', auth='none', save_session=False)
def health(self):
data = json.dumps({
'status': 'pass',
})
headers = [('Content-Type', 'application/json'),
('Cache-Control', 'no-store')]
return request.make_response(data, headers)
@route('/websocket/peek_notifications', type='json', auth='public', cors='*')
def peek_notifications(self, channels, last, is_first_poll=False):
if not all(isinstance(c, str) for c in channels):
raise ValueError("bus.Bus only string channels are allowed.")
if is_first_poll:
# Used to detect when the current session is expired.
request.session['is_websocket_session'] = True
elif 'is_websocket_session' not in request.session:
raise SessionExpiredException()
channels = list(set(
channel_with_db(request.db, c)
for c in request.env['ir.websocket']._build_bus_channel_list(channels)
))
last_known_notification_id = request.env['bus.bus'].sudo().search([], limit=1, order='id desc').id or 0
if last > last_known_notification_id:
last = 0
notifications = request.env['bus.bus']._poll(channels, last)
return {'channels': channels, 'notifications': notifications}
@route('/websocket/update_bus_presence', type='json', auth='public', cors='*')
def update_bus_presence(self, inactivity_period, im_status_ids_by_model):
if 'is_websocket_session' not in request.session:
raise SessionExpiredException()
request.env['ir.websocket']._update_bus_presence(int(inactivity_period), im_status_ids_by_model)
return {}
@route('/bus/websocket_worker_bundle', type='http', auth='public', cors='*')
def get_websocket_worker_bundle(self, v=None): # pylint: disable=unused-argument
"""
:param str v: Version of the worker, frontend only argument used to
prevent new worker versions to be loaded from the browser cache.
"""
bundle_name = 'bus.websocket_worker_assets'
bundle = request.env["ir.qweb"]._get_asset_bundle(bundle_name, debug_assets="assets" in request.session.debug)
stream = request.env['ir.binary']._get_stream_from(bundle.js())
return stream.get_response()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<odoo>
<record id="ir_actions_server_resend_whatsapp_queue" model="ir.actions.server">
<field name="name">WhatsApp : Resend failed Messages</field>
<field name="model_id" ref="whatsapp.model_whatsapp_message"/>
<field name="state">code</field>
<field name="binding_view_types">list</field>
<field name="code">action = records._resend_failed()</field>
<field name="binding_model_id" eval="ref('whatsapp.model_whatsapp_message')"/>
<field name="binding_type">action</field>
</record>
</odoo>

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<odoo>
<data>
<record id="ir_cron_send_whatsapp_queue" model="ir.cron">
<field name="name">WhatsApp : Send In Queue Messages</field>
<field name="model_id" ref="whatsapp.model_whatsapp_message"/>
<field name="state">code</field>
<field name="code">model._send_cron()</field>
<field name='interval_number'>1</field>
<field name='interval_type'>hours</field>
<field name="numbercall">-1</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.module.category" id="module_whatsapp">
<field name="name">WhatsApp</field>
<field name="description">User access levels for WhatsApp module</field>
<field name="sequence">10</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,2 @@
UPDATE whatsapp_account
SET token = 'dummy_token';

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="template_message_preview" t-name="WhatsApp Preview">
<div class="o_whatsapp_preview overflow-hidden ps-3 pe-5">
<div class="o_whatsapp_message mt-2 mb-1 fs-6 lh-1 float-start text-break text-black position-relative">
<div class="o_whatsapp_message_core p-2 position-relative">
<div class="o_whatsapp_message_header bg-opacity-50" t-if="header_type != 'none' and header_type != 'text'">
<div t-attf-class="d-block bg-400 p-4 text-center {{ 'rounded-top-2' if header_type == 'location' else 'rounded-2' }}">
<img class="m-2 img-fluid" t-attf-src="/whatsapp/static/img/{{header_type}}.png" t-att-alt="header_type"/>
</div>
<div t-if="header_type == 'location'" class="o_whatsapp_location_footer d-flex p-2 bg-200 rounded-bottom-2 flex-column">
<span class="o-whatsapp-font-11">{{Location name}}</span><br/>
<span class="text-600 o-whatsapp-font-9">{{Address}}</span>
</div>
</div>
<div class="o_whatsapp_message_body px-1 mt-2" t-attf-style="direction:{{language_direction}};">
<t t-out="body"/>
</div>
<div t-if="footer_text" class="o_whatsapp_message_footer px-1">
<span class="fs-6 text-400" t-out="footer_text" />
<span class="o_whatsapp_msg_space me-5 d-inline-block"/>
</div>
<span class="position-absolute bottom-0 end-0 o-whatsapp-font-11 py-1 px-2 text-black-50" area-hidden="true">
06:00
</span>
</div>
<div class="o_whatsapp_message_links cursor-default px-2" t-if="buttons">
<hr class="position-relative w-100 m-0"/>
<t t-foreach="buttons" t-as="button">
<t t-if="button.sequence &lt; 2">
<span t-attf-class="o_whatsapp_message_link d-block text-center my-3">
<t t-if="button.button_type=='phone_number'">
<i t-attf-class="fa fs-5 fa-phone"/>
</t>
<t t-elif="button.button_type=='url'">
<i t-attf-class="fa fs-5 fa-external-link"/>
</t>
<t t-else="">
<i t-attf-class="fa fs-5 fa-reply"/>
</t>
<t t-out="button.name"/>
</span>
</t>
<t t-if="button.sequence == 2">
<span t-attf-class="o_whatsapp_message_link d-block text-center my-3">
<i t-attf-class="fa fs-5 fa-list-ul"/> See all options
</span>
</t>
</t>
</div>
</div>
</div>
</template>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More