This commit is contained in:
gsinghpal
2026-03-14 12:04:20 -04:00
parent fc3c966484
commit e9cf75ee48
75 changed files with 6991 additions and 873 deletions

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Chatter Enhance',
'version': '19.0.1.0.0',
'category': 'Productivity',
'summary': 'Resizable, collapsible chatter panel with per-user position preference.',
'description': """
Fusion Chatter Enhance
======================
Enhances the Odoo chatter panel with:
- Drag-to-resize: grab the panel edge to change width
- Quick toggle: hover button to show/hide the chatter
- Bottom position: move chatter below the form in user preferences
- Per-user setting: each user picks their own chatter layout
- Icon-only topbar buttons with tooltips for compact display
Copyright 2024-2026 Nexa Systems Inc. All rights reserved.
""",
'author': 'Nexa Systems Inc.',
'website': 'https://www.nexasystems.ca',
'license': 'OPL-1',
'depends': [
'base',
'mail',
],
'data': [
'views/res_users_views.xml',
'views/templates.xml',
],
'assets': {
'web.assets_backend': [
'fusion_chatter_enhance/static/src/scss/chatter_enhance.scss',
'fusion_chatter_enhance/static/src/js/chatter_panel.js',
],
},
'installable': True,
'auto_install': False,
'application': False,
}

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import ir_http
from . import res_users

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1
from odoo import models
from odoo.http import request
class IrHttp(models.AbstractModel):
_inherit = 'ir.http'
def session_info(self):
result = super().session_info()
if request.session.uid:
user = self.env.user
result['chatter_position'] = user.chatter_position or 'side'
result['chatter_hidden'] = user.chatter_hidden or False
return result

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1
from odoo import fields, models
class ResUsers(models.Model):
_inherit = 'res.users'
chatter_position = fields.Selection(
selection=[
('side', 'Side (right of form)'),
('bottom', 'Bottom (below form)'),
],
string='Chatter Position',
default='side',
)
chatter_hidden = fields.Boolean(
string='Chatter Hidden',
default=False,
)
@property
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS + ['chatter_position', 'chatter_hidden']
@property
def SELF_WRITEABLE_FIELDS(self):
return super().SELF_WRITEABLE_FIELDS + ['chatter_position', 'chatter_hidden']

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -0,0 +1,424 @@
// Fusion Chatter Enhance
// Copyright 2024-2026 Nexa Systems Inc.
// License OPL-1
//
// Resizable & collapsible chatter panel with per-user position preference.
//
// ARCHITECTURE:
// 1. On script load: read localStorage, inject <style> for anti-flash.
// 2. On DOMContentLoaded: read odoo.__session_info__ (server-injected),
// sync to localStorage, then apply the definitive layout.
// 3. MutationObserver re-applies layout on SPA navigations.
// 4. No RPC calls needed -- preferences come from session_info.
(function () {
'use strict';
var STORAGE_KEY_WIDTH = 'fce_chatter_width';
var STORAGE_KEY_HIDDEN = 'fce_chatter_hidden';
var STORAGE_KEY_POSITION = 'fce_chatter_position';
var MIN_CHATTER_PCT = 15;
var MAX_CHATTER_PCT = 50;
var DEFAULT_CHATTER_PCT = 20;
var STYLE_TAG_ID = 'fce-width-override';
var TOOLTIPS = {
'.o-mail-Chatter-sendMessage': 'Send Message',
'.o-mail-Chatter-logNote': 'Log Note',
'button[data-hotkey="shift+w"]': 'WhatsApp',
'.o-mail-Chatter-activity': 'Schedule Activity',
};
var _enhanceTimer = null;
var _sessionSynced = false;
// =========================================================================
// EARLY <style> INJECTION (runs before DOM is ready)
// =========================================================================
var SEL_RENDERER = '.o_action_manager .o_form_view .o_form_renderer';
var SEL_SHEET = SEL_RENDERER + ':not(.fce-chatter-bottom) > .o_form_sheet_bg';
var SEL_CHATTER = SEL_RENDERER + ':not(.fce-chatter-bottom) > .o-mail-ChatterContainer, ' +
SEL_RENDERER + ':not(.fce-chatter-bottom) > .o-mail-Form-chatter, ' +
SEL_RENDERER + ':not(.fce-chatter-bottom) > .o-aside';
function buildWidthCss(chatterPct) {
var formPct = 100 - chatterPct;
return '@media (min-width:992px){' +
SEL_SHEET + '{flex:0 0 ' + formPct + '% !important;width:' + formPct + '% !important;max-width:' + formPct + '% !important;}' +
SEL_CHATTER + '{flex:0 0 ' + chatterPct + '% !important;width:' + chatterPct + '% !important;}' +
'}';
}
function injectOrUpdateStyleTag(css) {
var tag = document.getElementById(STYLE_TAG_ID);
if (!tag) {
tag = document.createElement('style');
tag.id = STYLE_TAG_ID;
tag.setAttribute('type', 'text/css');
(document.head || document.documentElement).appendChild(tag);
}
tag.textContent = css;
}
// =========================================================================
// SESSION INFO READER
// =========================================================================
function getSessionInfo() {
try {
var o = window.odoo;
if (!o) return null;
return o.__session_info__ || o.session_info || null;
} catch (_) {}
return null;
}
function syncFromSessionInfo() {
if (_sessionSynced) return;
var si = getSessionInfo();
if (!si || !si.uid) return;
_sessionSynced = true;
if (si.chatter_position) {
localStorage.setItem(STORAGE_KEY_POSITION, si.chatter_position);
}
if (typeof si.chatter_hidden === 'boolean') {
localStorage.setItem(STORAGE_KEY_HIDDEN, si.chatter_hidden ? '1' : '0');
}
}
function applyEarlyStyles() {
syncFromSessionInfo();
var pos = localStorage.getItem(STORAGE_KEY_POSITION) || 'side';
if (pos === 'bottom') {
injectOrUpdateStyleTag('');
return;
}
if (localStorage.getItem(STORAGE_KEY_HIDDEN) === '1') {
injectOrUpdateStyleTag(buildWidthCss(0));
return;
}
var raw = localStorage.getItem(STORAGE_KEY_WIDTH);
var pct = raw ? parseFloat(raw) : NaN;
if (isNaN(pct) || pct < MIN_CHATTER_PCT || pct > MAX_CHATTER_PCT) pct = DEFAULT_CHATTER_PCT;
injectOrUpdateStyleTag(buildWidthCss(pct));
}
applyEarlyStyles();
// =========================================================================
// WRITE HELPERS (save to backend)
// =========================================================================
function getSessionUid() {
var si = getSessionInfo();
return si && si.uid ? si.uid : null;
}
function rpcCall(model, method, args, kwargs, callback) {
try {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/web/dataset/call_kw/' + model + '/' + method, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status !== 200) { if (callback) callback(null); return; }
try {
var resp = JSON.parse(xhr.responseText);
if (resp && resp.error) { if (callback) callback(null); return; }
if (callback) callback(resp && resp.result);
} catch (_) {
if (callback) callback(null);
}
};
xhr.send(JSON.stringify({
jsonrpc: '2.0',
method: 'call',
params: { model: model, method: method, args: args, kwargs: kwargs || {} },
}));
} catch (_) {
if (callback) callback(null);
}
}
function saveHiddenPref(hidden) {
localStorage.setItem(STORAGE_KEY_HIDDEN, hidden ? '1' : '0');
var uid = getSessionUid();
if (!uid) return;
rpcCall('res.users', 'write', [[uid], { chatter_hidden: hidden }], {}, null);
}
// =========================================================================
// STORAGE HELPERS
// =========================================================================
function getStoredWidth() {
var v = localStorage.getItem(STORAGE_KEY_WIDTH);
var n = v ? parseFloat(v) : NaN;
if (isNaN(n) || n < MIN_CHATTER_PCT || n > MAX_CHATTER_PCT) return DEFAULT_CHATTER_PCT;
return n;
}
function getStoredPosition() {
return localStorage.getItem(STORAGE_KEY_POSITION) || 'side';
}
function isHidden() {
return localStorage.getItem(STORAGE_KEY_HIDDEN) === '1';
}
// =========================================================================
// DOM FINDERS
// =========================================================================
function findFormRenderer() {
return document.querySelector('.o_action_manager .o_form_view .o_form_renderer');
}
function findChatterEl(renderer) {
if (!renderer) return null;
return renderer.querySelector('.o-mail-ChatterContainer') ||
renderer.querySelector('.o-mail-Form-chatter') ||
renderer.querySelector('.o-aside');
}
function findSheetBg(renderer) {
if (!renderer) return null;
return renderer.querySelector('.o_form_sheet_bg');
}
// =========================================================================
// WIDTH APPLICATION
// =========================================================================
function applyWidths(renderer, chatterPct) {
injectOrUpdateStyleTag(buildWidthCss(chatterPct));
var sheet = findSheetBg(renderer);
var chatter = findChatterEl(renderer);
if (!sheet || !chatter) return;
var formPct = 100 - chatterPct;
sheet.style.flex = '0 0 ' + formPct + '%';
sheet.style.width = formPct + '%';
sheet.style.maxWidth = formPct + '%';
sheet.style.minWidth = '0';
chatter.style.flex = '0 0 ' + chatterPct + '%';
chatter.style.width = chatterPct + '%';
}
function clearInlineWidths(renderer) {
var sheet = findSheetBg(renderer);
var chatter = findChatterEl(renderer);
if (sheet) sheet.removeAttribute('style');
if (chatter) chatter.removeAttribute('style');
}
// =========================================================================
// RESIZE HANDLE
// =========================================================================
function injectResizeHandle(renderer) {
if (renderer.querySelector('.fce-resize-handle')) return;
var chatter = findChatterEl(renderer);
if (!chatter) return;
var handle = document.createElement('div');
handle.className = 'fce-resize-handle';
chatter.parentNode.insertBefore(handle, chatter);
var startX = 0;
var startWidth = 0;
function onMouseDown(e) {
e.preventDefault();
startX = e.clientX;
startWidth = chatter.getBoundingClientRect().width;
handle.classList.add('fce-dragging');
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
}
function onMouseMove(e) {
var delta = startX - e.clientX;
var rendererWidth = renderer.getBoundingClientRect().width;
var newWidth = startWidth + delta;
var pct = (newWidth / rendererWidth) * 100;
pct = Math.max(MIN_CHATTER_PCT, Math.min(MAX_CHATTER_PCT, pct));
applyWidths(renderer, pct);
}
function onMouseUp() {
handle.classList.remove('fce-dragging');
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
var rendererWidth = renderer.getBoundingClientRect().width;
var chatterWidth = chatter.getBoundingClientRect().width;
var pct = Math.round((chatterWidth / rendererWidth) * 100);
localStorage.setItem(STORAGE_KEY_WIDTH, String(pct));
injectOrUpdateStyleTag(buildWidthCss(pct));
}
handle.addEventListener('mousedown', onMouseDown);
}
// =========================================================================
// TOGGLE / EXPAND BUTTONS
// =========================================================================
function injectToggleBtn(renderer) {
if (renderer.querySelector('.fce-toggle-btn') || renderer.querySelector('.fce-expand-btn')) return;
var chatter = findChatterEl(renderer);
if (!chatter) return;
var btn = document.createElement('button');
btn.className = 'fce-toggle-btn';
btn.type = 'button';
btn.innerHTML = '<i class="fa fa-chevron-right"></i>';
btn.title = 'Hide chatter';
chatter.style.position = 'relative';
chatter.appendChild(btn);
btn.addEventListener('click', function () {
saveHiddenPref(true);
renderer.classList.add('fce-chatter-hidden');
injectOrUpdateStyleTag(buildWidthCss(0));
btn.remove();
injectExpandBtn(renderer);
});
}
function injectExpandBtn(renderer) {
if (renderer.querySelector('.fce-expand-btn')) return;
var btn = document.createElement('button');
btn.className = 'fce-expand-btn';
btn.type = 'button';
btn.innerHTML = '<i class="fa fa-chevron-left"></i>';
btn.title = 'Show chatter';
renderer.appendChild(btn);
btn.addEventListener('click', function () {
saveHiddenPref(false);
renderer.classList.remove('fce-chatter-hidden');
btn.remove();
var savedPct = getStoredWidth();
applyWidths(renderer, savedPct);
injectResizeHandle(renderer);
injectToggleBtn(renderer);
});
}
// =========================================================================
// TOOLTIPS
// =========================================================================
function applyTooltips() {
var keys = Object.keys(TOOLTIPS);
for (var i = 0; i < keys.length; i++) {
var selector = keys[i];
var title = TOOLTIPS[selector];
var els = document.querySelectorAll(selector);
for (var j = 0; j < els.length; j++) {
if (!els[j].getAttribute('title')) els[j].setAttribute('title', title);
}
}
}
// =========================================================================
// LAYOUT APPLICATION
// =========================================================================
function applyLayout(renderer) {
var pos = getStoredPosition();
var hidden = isHidden();
if (pos === 'bottom') {
renderer.classList.add('fce-chatter-bottom');
renderer.classList.remove('fce-chatter-hidden');
injectOrUpdateStyleTag('');
removeEl(renderer, '.fce-resize-handle');
removeEl(renderer, '.fce-toggle-btn');
removeEl(renderer, '.fce-expand-btn');
renderer.style.display = 'block';
var sheet = findSheetBg(renderer);
var chatter = findChatterEl(renderer);
if (sheet) {
sheet.style.flex = 'none';
sheet.style.width = '100%';
sheet.style.maxWidth = '100%';
sheet.style.minWidth = '100%';
}
if (chatter) {
chatter.style.flex = 'none';
chatter.style.width = '100%';
chatter.style.maxWidth = '100%';
chatter.style.minWidth = '100%';
}
return;
}
renderer.classList.remove('fce-chatter-bottom');
renderer.style.display = '';
if (hidden) {
renderer.classList.add('fce-chatter-hidden');
injectOrUpdateStyleTag(buildWidthCss(0));
removeEl(renderer, '.fce-resize-handle');
removeEl(renderer, '.fce-toggle-btn');
injectExpandBtn(renderer);
return;
}
renderer.classList.remove('fce-chatter-hidden');
removeEl(renderer, '.fce-expand-btn');
var savedPct = getStoredWidth();
applyWidths(renderer, savedPct);
injectResizeHandle(renderer);
injectToggleBtn(renderer);
applyTooltips();
}
function removeEl(renderer, selector) {
var el = renderer.querySelector(selector);
if (el) el.remove();
}
// =========================================================================
// MAIN ORCHESTRATOR
// =========================================================================
function enhance() {
var renderer = findFormRenderer();
if (!renderer) return;
var chatter = findChatterEl(renderer);
if (!chatter) return;
if (window.innerWidth < 992) return;
syncFromSessionInfo();
applyLayout(renderer);
}
function debouncedEnhance() {
if (_enhanceTimer) clearTimeout(_enhanceTimer);
_enhanceTimer = setTimeout(enhance, 150);
}
var observer = new MutationObserver(debouncedEnhance);
function boot() {
syncFromSessionInfo();
applyEarlyStyles();
observer.observe(document.body, { childList: true, subtree: true });
enhance();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();

View File

@@ -0,0 +1,341 @@
// Fusion Chatter Enhance - Styles
// Copyright 2024-2026 Nexa Systems Inc.
// License OPL-1
//
// NOTE: The chatter/form flex percentages are NOT set here.
// They are injected by chatter_panel.js as a <style> tag in <head>
// from localStorage, so the correct width is applied BEFORE first paint.
// =============================================================================
// FORM LAYOUT FOUNDATION (flex row, no-wrap) -- percentages come from JS
// =============================================================================
@media (min-width: 992px) {
.o_action_manager > .o_action > .o_form_view .o_form_renderer:not(.fce-chatter-bottom) {
display: flex !important;
flex-wrap: nowrap !important;
> .o_form_sheet_bg {
min-width: 0 !important;
}
> .o-mail-ChatterContainer,
> .o-mail-Form-chatter,
> .o-aside {
min-width: 200px !important;
max-width: 50% !important;
position: relative;
}
}
.o_action_manager .o_form_sheet_bg {
max-width: none !important;
}
.o_action_manager .o_form_sheet {
max-width: none !important;
width: 100% !important;
}
}
// =============================================================================
// CHATTER BOTTOM LAYOUT (per-user preference)
// High specificity to override Odoo's flex layout.
// =============================================================================
.o_action_manager .o_form_view .o_form_renderer.fce-chatter-bottom {
display: block !important;
flex-direction: column !important;
flex-wrap: wrap !important;
> .o_form_sheet_bg {
flex: none !important;
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
}
> .o-mail-ChatterContainer,
> .o-mail-Form-chatter,
> .o-aside {
flex: none !important;
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
}
> .fce-resize-handle {
display: none !important;
}
}
// =============================================================================
// CHATTER HIDDEN STATE
// =============================================================================
.fce-chatter-hidden {
> .o-mail-ChatterContainer,
> .o-mail-Form-chatter,
> .o-aside {
display: none !important;
}
> .o_form_sheet_bg {
flex: 1 1 100% !important;
width: 100% !important;
max-width: 100% !important;
}
}
// =============================================================================
// RESIZE HANDLE
// Idle: subtle dotted line so users know where to grab.
// Hover: dots merge into a solid line with a glow pulse.
// Dragging: bold solid accent bar.
// =============================================================================
.fce-resize-handle {
width: 7px;
cursor: col-resize;
background: transparent;
position: relative;
z-index: 10;
flex-shrink: 0;
align-self: stretch;
// --- idle dotted line (always visible) ---
&::before {
content: '';
position: absolute;
top: 10%;
bottom: 10%;
left: 50%;
transform: translateX(-50%);
width: 2px;
border: none;
background-image: repeating-linear-gradient(
to bottom,
var(--o-border-color, #ced4da) 0px,
var(--o-border-color, #ced4da) 3px,
transparent 3px,
transparent 8px
);
opacity: 0.45;
border-radius: 1px;
transition: opacity 0.3s ease, background-image 0.3s ease, width 0.25s ease,
box-shadow 0.3s ease, top 0.3s ease, bottom 0.3s ease;
}
// --- hover grip dots (centered cluster) ---
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 5px;
height: 28px;
opacity: 0;
border-radius: 3px;
background-image: radial-gradient(
circle,
var(--o-border-color, #adb5bd) 1.2px,
transparent 1.2px
);
background-size: 5px 7px;
background-repeat: repeat-y;
background-position: center;
transition: opacity 0.25s ease, height 0.3s ease, transform 0.25s ease;
}
// --- HOVER STATE ---
&:hover {
&::before {
opacity: 0.85;
width: 2px;
top: 4%;
bottom: 4%;
background-image: repeating-linear-gradient(
to bottom,
var(--o-action, #0077b6) 0px,
var(--o-action, #0077b6) 3px,
transparent 3px,
transparent 8px
);
box-shadow: 0 0 6px rgba(0, 119, 182, 0.2);
}
&::after {
opacity: 1;
height: 36px;
}
}
// --- DRAGGING STATE ---
&.fce-dragging {
&::before {
opacity: 1;
width: 3px;
top: 0;
bottom: 0;
background-image: none;
background-color: var(--o-action, #0077b6);
box-shadow: 0 0 10px rgba(0, 119, 182, 0.35);
border-radius: 2px;
}
&::after {
opacity: 0;
}
}
}
// =============================================================================
// TOGGLE BUTTON
// =============================================================================
.fce-toggle-btn {
position: absolute;
top: 8px;
left: -14px;
width: 28px;
height: 28px;
border-radius: 50%;
border: 1px solid var(--o-border-color, #dee2e6);
background: var(--o-bg-card, #fff);
color: var(--o-text-muted, #6c757d);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 20;
opacity: 0;
transition: opacity 0.2s ease, background-color 0.15s ease, color 0.15s ease;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
font-size: 12px;
padding: 0;
&:hover {
background: var(--o-action, #0077b6);
color: #fff;
border-color: var(--o-action, #0077b6);
}
}
.o-mail-ChatterContainer:hover .fce-toggle-btn,
.o-mail-Form-chatter:hover .fce-toggle-btn,
.o-aside:hover .fce-toggle-btn {
opacity: 1;
}
.fce-expand-btn {
position: fixed;
right: 8px;
top: 50%;
transform: translateY(-50%);
width: 28px;
height: 56px;
border-radius: 6px 0 0 6px;
border: 1px solid var(--o-border-color, #dee2e6);
border-right: none;
background: var(--o-bg-card, #fff);
color: var(--o-text-muted, #6c757d);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 20;
box-shadow: -2px 0 6px rgba(0, 0, 0, 0.08);
font-size: 14px;
padding: 0;
transition: background-color 0.15s ease, color 0.15s ease;
&:hover {
background: var(--o-action, #0077b6);
color: #fff;
}
}
// =============================================================================
// COMPACT CHATTER CONTENT
// =============================================================================
.o-mail-Thread .o-mail-Message {
padding: 6px 10px !important;
font-size: 0.9em;
}
.o-mail-Activity {
padding: 4px 8px !important;
}
// =============================================================================
// ICON-ONLY TOPBAR BUTTONS
// =============================================================================
.o-mail-Chatter-topbar {
gap: 4px;
.o-mail-Chatter-sendMessage {
font-size: 0 !important;
padding: 8px 12px !important;
min-width: auto;
line-height: 1;
&::before {
font-family: "Font Awesome 5 Free", FontAwesome;
font-weight: 900;
font-size: 15px;
content: "\f0e0";
}
}
.o-mail-Chatter-logNote {
font-size: 0 !important;
padding: 8px 12px !important;
min-width: auto;
line-height: 1;
&::before {
font-family: "Font Awesome 5 Free", FontAwesome;
font-weight: 900;
font-size: 15px;
content: "\f044";
}
}
button[data-hotkey="shift+w"] {
> span { display: none !important; }
padding: 8px 12px !important;
min-width: auto;
line-height: 1;
&::before {
content: "";
display: inline-block;
width: 17px;
height: 17px;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z'/%3E%3C/svg%3E");
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
background-color: currentColor;
}
}
.o-mail-Chatter-activity {
> span { display: none !important; }
padding: 8px 12px !important;
min-width: auto;
line-height: 1;
&::before {
font-family: "Font Awesome 5 Free", FontAwesome;
font-weight: 900;
font-size: 15px;
content: "\f073";
}
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add chatter preferences to the "other_preferences" group in user preferences form -->
<record id="view_users_form_chatter_prefs" model="ir.ui.view">
<field name="name">res.users.form.chatter.prefs</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form_simple_modif"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='other_preferences']" position="inside">
<label for="chatter_position"/>
<div>
<field name="chatter_position" widget="radio"/>
</div>
<label for="chatter_hidden"/>
<div>
<field name="chatter_hidden"/>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--
Inject chatter preferences into localStorage BEFORE the asset bundle loads.
This runs as an inline script right after the Odoo bootstrap script,
guaranteeing the values are available before our IIFE in the bundle executes.
-->
<template id="chatter_prefs_inject" inherit_id="web.layout" name="Chatter Preferences Early Inject">
<xpath expr="//script[@id='web.layout.odooscript']" position="after">
<t t-if="request.session.uid">
<script type="text/javascript">
(function(){
try {
localStorage.setItem('fce_chatter_position', '<t t-esc="request.env.user.chatter_position or 'side'"/>');
localStorage.setItem('fce_chatter_hidden', '<t t-esc="'1' if request.env.user.chatter_hidden else '0'"/>');
} catch(e){}
})();
</script>
</t>
</xpath>
</template>
</odoo>