
/* /web/static/src/views/view_dialogs/form_view_dialog.js */
odoo.define('@web/views/view_dialogs/form_view_dialog',['@web/core/dialog/dialog','@web/core/utils/hooks','@web/search/action_hook','@web/views/view','@odoo/owl'],function(require){'use strict';let __exports={};const{Dialog}=require("@web/core/dialog/dialog");const{useChildRef,useService}=require("@web/core/utils/hooks");const{CallbackRecorder}=require("@web/search/action_hook");const{View}=require("@web/views/view");const{Component,onMounted}=require("@odoo/owl");const FormViewDialog=__exports.FormViewDialog=class FormViewDialog extends Component{static template="web.FormViewDialog";static components={Dialog,View};static props={close:Function,resModel:String,context:{type:Object,optional:true},nextRecordsContext:{type:Object,optional:true},mode:{optional:true,validate:(m)=>["edit","readonly"].includes(m),},onRecordSaved:{type:Function,optional:true},onRecordDiscarded:{type:Function,optional:true},removeRecord:{type:Function,optional:true},resId:{type:[Number,Boolean],optional:true},title:{type:String,optional:true},viewId:{type:[Number,Boolean],optional:true},preventCreate:{type:Boolean,optional:true},preventEdit:{type:Boolean,optional:true},isToMany:{type:Boolean,optional:true},size:Dialog.props.size,};static defaultProps={onRecordSaved:()=>{},preventCreate:false,preventEdit:false,isToMany:false,};setup(){super.setup();this.actionService=useService("action");this.modalRef=useChildRef();this.env.dialogData.dismiss=()=>this.discardRecord();const buttonTemplate=this.props.isToMany?"web.FormViewDialog.ToMany.buttons":"web.FormViewDialog.ToOne.buttons";this.currentResId=this.props.resId;this.viewProps={type:"form",buttonTemplate,context:this.props.context||{},display:{controlPanel:false},mode:this.props.mode||"edit",resId:this.props.resId||false,resModel:this.props.resModel,viewId:this.props.viewId||false,preventCreate:this.props.preventCreate,preventEdit:this.props.preventEdit,discardRecord:this.discardRecord.bind(this),saveRecord:async(record,{saveAndNew})=>{const saved=await record.save({reload:false});if(saved){this.currentResId=record.resId;await this.props.onRecordSaved(record);if(saveAndNew){this.currentResId=false;const context=this.props.nextRecordsContext||this.props.context||{};await record.model.load({resId:false,context});}else{this.props.close();}}
return saved;},__beforeLeave__:new CallbackRecorder(),};if(this.props.removeRecord){this.viewProps.removeRecord=async()=>{await this.props.removeRecord();this.props.close();};}
onMounted(()=>{if(this.modalRef.el.querySelector(".modal-footer").childElementCount>1){const defaultButton=this.modalRef.el.querySelector(".modal-footer button.o-default-button");if(defaultButton){defaultButton.classList.add("d-none");}}});}
async discardRecord(){if(this.props.onRecordDiscarded){await this.props.onRecordDiscarded();}
this.props.close();}
async onExpand(){const beforeLeaveCallbacks=this.viewProps.__beforeLeave__.callbacks;const res=await Promise.all(beforeLeaveCallbacks.map((callback)=>callback()));if(!res.includes(false)){this.actionService.doAction({type:"ir.actions.act_window",res_model:this.props.resModel,res_id:this.currentResId,views:[[false,"form"]],context:{...this.props.context,},});}}}
return __exports;});;

/* /web/static/src/core/debug/debug_context.js */
odoo.define('@web/core/debug/debug_context',['@web/core/user','@web/core/registry','@odoo/owl'],function(require){'use strict';let __exports={};const{user}=require("@web/core/user");const{registry}=require("@web/core/registry");const{useEffect,useEnv,useSubEnv}=require("@odoo/owl");const debugRegistry=registry.category("debug");const getAccessRights=async()=>{const rightsToCheck={"ir.ui.view":"write","ir.rule":"read","ir.model.access":"read",};const proms=Object.entries(rightsToCheck).map(([model,operation])=>{return user.checkAccessRight(model,operation);});const[canEditView,canSeeRecordRules,canSeeModelAccess]=await Promise.all(proms);const accessRights={canEditView,canSeeRecordRules,canSeeModelAccess};return accessRights;};class DebugContext{constructor(defaultCategories){this.categories=new Map(defaultCategories.map((cat)=>[cat,[{}]]));}
activateCategory(category,context){const contexts=this.categories.get(category)||new Set();contexts.add(context);this.categories.set(category,contexts);return()=>{contexts.delete(context);if(contexts.size===0){this.categories.delete(category);}};}
async getItems(env){const accessRights=await getAccessRights();return[...this.categories.entries()].flatMap(([category,contexts])=>{return debugRegistry.category(category).getAll().map((factory)=>factory(Object.assign({env,accessRights},...contexts)));}).filter(Boolean).sort((x,y)=>{const xSeq=x.sequence||1000;const ySeq=y.sequence||1000;return xSeq-ySeq;});}}
const debugContextSymbol=Symbol("debugContext");__exports.createDebugContext=createDebugContext;function createDebugContext({categories=[]}={}){return{[debugContextSymbol]:new DebugContext(categories)};}
__exports.useOwnDebugContext=useOwnDebugContext;function useOwnDebugContext({categories=[]}={}){useSubEnv(createDebugContext({categories}));}
__exports.useEnvDebugContext=useEnvDebugContext;function useEnvDebugContext(){const debugContext=useEnv()[debugContextSymbol];if(!debugContext){throw new Error("There is no debug context available in the current environment.");}
return debugContext;}
__exports.useDebugCategory=useDebugCategory;function useDebugCategory(category,context={}){const env=useEnv();if(env.debug){const debugContext=useEnvDebugContext();useEffect(()=>debugContext.activateCategory(category,context),()=>[]);}}
return __exports;});;

/* /web/static/src/core/debug/debug_menu.js */
odoo.define('@web/core/debug/debug_menu',['@web/core/l10n/translation','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_item','@web/core/debug/debug_menu_basic','@web/core/commands/command_hook','@web/core/utils/hooks','@web/core/debug/debug_context'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{Dropdown}=require("@web/core/dropdown/dropdown");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{DebugMenuBasic}=require("@web/core/debug/debug_menu_basic");const{useCommand}=require("@web/core/commands/command_hook");const{useService}=require("@web/core/utils/hooks");const{useEnvDebugContext}=require("@web/core/debug/debug_context");const DebugMenu=__exports.DebugMenu=class DebugMenu extends DebugMenuBasic{static components={Dropdown,DropdownItem};static props={};setup(){super.setup();const debugContext=useEnvDebugContext();this.command=useService("command");useCommand(_t("Debug tools..."),async()=>{const items=await debugContext.getItems(this.env);let index=0;const defaultCategories=items.filter((item)=>item.type==="separator").map(()=>(index+=1));const provider={async provide(){const categories=[...defaultCategories];let category=categories.shift();const result=[];items.forEach((item)=>{if(item.type==="item"){result.push({name:item.description.toString(),action:item.callback,category,});}else if(item.type==="separator"){category=categories.shift();}});return result;},};const configByNamespace={default:{categories:defaultCategories,emptyMessage:_t("No debug command found"),placeholder:_t("Choose a debug command..."),},};const commandPaletteConfig={configByNamespace,providers:[provider],};return commandPaletteConfig;},{category:"debug",});}}
return __exports;});;

/* /web/static/src/core/debug/debug_menu_basic.js */
odoo.define('@web/core/debug/debug_menu_basic',['@web/core/debug/debug_context','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_item','@web/core/l10n/translation','@web/core/utils/arrays','@odoo/owl','@web/core/registry'],function(require){'use strict';let __exports={};const{useEnvDebugContext}=require("@web/core/debug/debug_context");const{Dropdown}=require("@web/core/dropdown/dropdown");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{_t}=require("@web/core/l10n/translation");const{groupBy,sortBy}=require("@web/core/utils/arrays");const{Component}=require("@odoo/owl");const{registry}=require("@web/core/registry");const debugSectionRegistry=registry.category("debug_section");debugSectionRegistry.add("record",{label:_t("Record"),sequence:10}).add("records",{label:_t("Records"),sequence:10}).add("ui",{label:_t("User Interface"),sequence:20}).add("security",{label:_t("Security"),sequence:30}).add("testing",{label:_t("Testing"),sequence:40}).add("tools",{label:_t("Tools"),sequence:50});const DebugMenuBasic=__exports.DebugMenuBasic=class DebugMenuBasic extends Component{static template="web.DebugMenu";static components={Dropdown,DropdownItem,};static props={};setup(){this.debugContext=useEnvDebugContext();}
async loadGroupedItems(){const items=await this.debugContext.getItems(this.env);const sections=groupBy(items,(item)=>item.section||"");this.sectionEntries=sortBy(Object.entries(sections),([section])=>debugSectionRegistry.get(section,{sequence:50}).sequence);}
getSectionLabel(section){return debugSectionRegistry.get(section,{label:section}).label;}}
return __exports;});;

/* /web/static/src/core/debug/debug_menu_items.js */
odoo.define('@web/core/debug/debug_menu_items',['@web/core/l10n/translation','@web/core/browser/browser','@web/core/browser/router','@web/core/registry','@web/core/user'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{browser}=require("@web/core/browser/browser");const{router}=require("@web/core/browser/router");const{registry}=require("@web/core/registry");const{user}=require("@web/core/user");function activateTestsAssetsDebugging({env}){if(String(router.current.debug).includes("tests")){return;}
return{type:"item",description:_t("Activate Test Mode"),callback:()=>{router.pushState({debug:"assets,tests"},{reload:true});},sequence:580,section:"tools",};}
__exports.regenerateAssets=regenerateAssets;function regenerateAssets({env}){return{type:"item",description:_t("Regenerate Assets"),callback:async()=>{await env.services.orm.call("ir.attachment","regenerate_assets_bundles");browser.location.reload();},sequence:550,section:"tools",};}
__exports.becomeSuperuser=becomeSuperuser;function becomeSuperuser({env}){const becomeSuperuserURL=browser.location.origin+"/web/become";if(!user.isAdmin){return false;}
return{type:"item",description:_t("Become Superuser"),href:becomeSuperuserURL,callback:()=>{browser.open(becomeSuperuserURL,"_self");},sequence:560,section:"tools",};}
function leaveDebugMode(){return{type:"item",description:_t("Leave Debug Mode"),callback:()=>{router.pushState({debug:0},{reload:true});},sequence:650,};}
registry.category("debug").category("default").add("regenerateAssets",regenerateAssets).add("becomeSuperuser",becomeSuperuser).add("activateTestsAssetsDebugging",activateTestsAssetsDebugging).add("leaveDebugMode",leaveDebugMode);return __exports;});;

/* /web/static/src/core/debug/debug_providers.js */
odoo.define('@web/core/debug/debug_providers',['@web/core/l10n/translation','@web/core/registry','@web/core/browser/browser','@web/core/browser/router'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{browser}=require("@web/core/browser/browser");const{router}=require("@web/core/browser/router");const commandProviderRegistry=registry.category("command_provider");commandProviderRegistry.add("debug",{provide:(env,options)=>{const result=[];if(env.debug){if(!env.debug.includes("assets")){result.push({action(){router.pushState({debug:"assets"},{reload:true});},category:"debug",name:_t("Activate debug mode (with assets)"),});}
result.push({action(){router.pushState({debug:0},{reload:true});},category:"debug",name:_t("Deactivate debug mode"),});result.push({action(){browser.open("/web/tests?debug=assets");},category:"debug",name:_t("Run Unit Tests"),});}else{const debugKey="debug";if(options.searchValue.toLowerCase()===debugKey){result.push({action(){router.pushState({debug:"1"},{reload:true});},category:"debug",name:`${_t("Activate debug mode")} (${debugKey})`,});result.push({action(){router.pushState({debug:"assets"},{reload:true});},category:"debug",name:`${_t("Activate debug mode (with assets)")} (${debugKey})`,});}}
return result;},});return __exports;});;

/* /web/static/src/core/debug/debug_utils.js */
odoo.define('@web/core/debug/debug_utils',[],function(require){'use strict';let __exports={};__exports.editModelDebug=editModelDebug;function editModelDebug(env,title,model,id){return env.services.action.doAction({res_model:model,res_id:id,name:title,type:"ir.actions.act_window",views:[[false,"form"]],view_mode:"form",target:"current",});}
return __exports;});;

/* /web/static/src/core/commands/command_hook.js */
odoo.define('@web/core/commands/command_hook',['@web/core/utils/hooks','@odoo/owl'],function(require){'use strict';let __exports={};const{useService}=require("@web/core/utils/hooks");const{useEffect}=require("@odoo/owl");__exports.useCommand=useCommand;function useCommand(name,action,options={}){const commandService=useService("command");useEffect(()=>commandService.add(name,action,options),()=>[]);}
return __exports;});;

/* /web/static/src/model/model.js */
odoo.define('@web/model/model',['@web/core/user','@web/core/utils/hooks','@web/search/action_hook','@web/search/with_search/with_search','@web/model/sample_server','@odoo/owl'],function(require){'use strict';let __exports={};const{user}=require("@web/core/user");const{useBus,useService}=require("@web/core/utils/hooks");const{useSetupAction}=require("@web/search/action_hook");const{SEARCH_KEYS}=require("@web/search/with_search/with_search");const{buildSampleORM}=require("@web/model/sample_server");const{EventBus,onMounted,onWillStart,onWillUpdateProps,status,useComponent,}=require("@odoo/owl");const Model=__exports.Model=class Model{constructor(env,params,services){this.env=env;this.orm=services.orm;this.bus=new EventBus();this.setup(params,services);}
setup(){}
async load(){}
hasData(){return true;}
getGroups(){return null;}
notify(){this.bus.trigger("update");}}
Model.services=[];function getSearchParams(props){const params={};for(const key of SEARCH_KEYS){params[key]=props[key];}
return params;}
function usePostMountedServices(services){if(services.dialog){services.dialog=Object.create(services.dialog);const dialogAddOrigin=services.dialog.add;let dialogRequests=[];services.dialog.add=(...args)=>{const index=dialogRequests.push(args);return()=>{dialogRequests[index]=null;};};onMounted(()=>{services.dialog.add=dialogAddOrigin;for(const req of dialogRequests){if(req){dialogAddOrigin(...req);}}
dialogRequests=null;});}
return services;}
__exports.useModel=useModel;function useModel(ModelClass,params,options={}){const component=useComponent();let services={};for(const key of ModelClass.services){services[key]=useService(key);}
services.orm=services.orm||useService("orm");services=usePostMountedServices(services);const model=new ModelClass(component.env,params,services);onWillStart(async()=>{await options.beforeFirstLoad?.();return model.load(component.props);});onWillUpdateProps((nextProps)=>model.load(nextProps));return model;}
__exports.useModelWithSampleData=useModelWithSampleData;function useModelWithSampleData(ModelClass,params,options={}){const component=useComponent();if(!(ModelClass.prototype instanceof Model)){throw new Error(`the model class should extend Model`);}
let services={};for(const key of ModelClass.services){services[key]=useService(key);}
services.orm=services.orm||useService("orm");services=usePostMountedServices(services);if(!("isAlive"in params)){params.isAlive=()=>status(component)!=="destroyed";}
const model=new ModelClass(component.env,params,services);useBus(model.bus,"update",options.onUpdate||(()=>{component.render(true);}));const globalState=component.props.globalState||{};const localState=component.props.state||{};let useSampleModel=component.props.useSampleModel&&(!("useSampleModel"in globalState)||globalState.useSampleModel);model.useSampleModel=useSampleModel;const orm=model.orm;let sampleORM=localState.sampleORM;let started=false;async function load(props){const searchParams=getSearchParams(props);await model.load(searchParams);if(useSampleModel&&!model.hasData()){sampleORM=sampleORM||buildSampleORM(component.props.resModel,component.props.fields,user);model.orm=sampleORM;await model.load(searchParams);model.orm=orm;}else{useSampleModel=false;model.useSampleModel=useSampleModel;}
if(started){model.notify();}}
onWillStart(async()=>{if(options.onWillStart){await options.onWillStart();}
await load(component.props);if(options.onWillStartAfterLoad){await options.onWillStartAfterLoad();}
started=true;});onWillUpdateProps((nextProps)=>{useSampleModel=false;load(nextProps);});useSetupAction({getGlobalState(){if(component.props.useSampleModel){return{useSampleModel};}},getLocalState:()=>{return{sampleORM};},});return model;}
__exports._makeFieldFromPropertyDefinition=_makeFieldFromPropertyDefinition;function _makeFieldFromPropertyDefinition(name,definition,relatedPropertyField){return{...definition,name,propertyName:definition.name,relation:definition.comodel,relatedPropertyField,};}
__exports.addPropertyFieldDefs=addPropertyFieldDefs;async function addPropertyFieldDefs(orm,resModel,context,fields,groupBy){const proms=[];for(const gb of groupBy){if(gb in fields){continue;}
const[fieldName]=gb.split(".");const field=fields[fieldName];if(field?.type==="properties"){proms.push(orm.call(resModel,"get_property_definition",[gb],{context,}).then((definition)=>{fields[gb]=_makeFieldFromPropertyDefinition(gb,definition,field);}).catch(()=>{fields[gb]=_makeFieldFromPropertyDefinition(gb,{},field);}));}}
return Promise.all(proms);}
return __exports;});;

/* /web/static/src/model/record.js */
odoo.define('@web/model/record',['@web/core/utils/hooks','@web/core/utils/objects','@web/model/relational_model/relational_model','@web/model/relational_model/utils','@odoo/owl'],function(require){'use strict';let __exports={};const{useService}=require("@web/core/utils/hooks");const{pick}=require("@web/core/utils/objects");const{RelationalModel}=require("@web/model/relational_model/relational_model");const{getFieldsSpec}=require("@web/model/relational_model/utils");const{Component,xml,onWillStart,onWillUpdateProps,useState}=require("@odoo/owl");const defaultActiveField={attrs:{},options:{},domain:"[]",string:""};class StandaloneRelationalModel extends RelationalModel{load(params={}){if(params.values){const data=params.values;const config=this._getNextConfig(this.config,params);this.root=this._createRoot(config,data);this.config=config;return;}
return super.load(params);}}
class _Record extends Component{static template=xml`<t t-slot="default" record="model.root"/>`;static props=["slots","info","fields","values?"];setup(){this.orm=useService("orm");const resModel=this.props.info.resModel;const activeFields=this.getActiveFields();const modelParams={config:{resModel,fields:this.props.fields,isMonoRecord:true,activeFields,resId:this.props.info.resId,mode:this.props.info.mode,},hooks:{onRecordSaved:this.props.info.onRecordSaved||(()=>{}),onWillSaveRecord:this.props.info.onWillSaveRecord||(()=>{}),onRecordChanged:this.props.info.onRecordChanged||(()=>{}),},};const modelServices=Object.fromEntries(StandaloneRelationalModel.services.map((servName)=>{return[servName,useService(servName)];}));modelServices.orm=this.orm;this.model=useState(new StandaloneRelationalModel(this.env,modelParams,modelServices));const prepareLoadWithValues=async(values)=>{values=pick(values,...Object.keys(modelParams.config.activeFields));const proms=[];for(const fieldName in values){if(["one2many","many2many"].includes(this.props.fields[fieldName].type)){if(values[fieldName].length&&typeof values[fieldName][0]==="number"){const resModel=this.props.fields[fieldName].relation;const resIds=values[fieldName];const activeField=modelParams.config.activeFields[fieldName];if(activeField.related){const{activeFields,fields}=activeField.related;const fieldSpec=getFieldsSpec(activeFields,fields,{});const kwargs={context:activeField.context||{},specification:fieldSpec,};proms.push(this.orm.webRead(resModel,resIds,kwargs).then((records)=>{values[fieldName]=records;}));}}}
if(this.props.fields[fieldName].type==="many2one"){const loadDisplayName=async(resId)=>{const resModel=this.props.fields[fieldName].relation;const activeField=modelParams.config.activeFields[fieldName];const kwargs={context:activeField.context||{},specification:{display_name:{}},};const records=await this.orm.webRead(resModel,[resId],kwargs);return records[0].display_name;};if(typeof values[fieldName]==="number"){const prom=loadDisplayName(values[fieldName]);prom.then((displayName)=>{values[fieldName]={id:values[fieldName],display_name:displayName,};});proms.push(prom);}else if(Array.isArray(values[fieldName])){if(values[fieldName][1]===undefined){const prom=loadDisplayName(values[fieldName][0]);prom.then((displayName)=>{values[fieldName]={id:values[fieldName][0],display_name:displayName,};});proms.push(prom);}
values[fieldName]={id:values[fieldName][0],display_name:values[fieldName][1],};}}
await Promise.all(proms);}
return values;};onWillStart(async()=>{if(this.props.values){const values=await prepareLoadWithValues(this.props.values);return this.model.load({values});}else{return this.model.load();}});onWillUpdateProps(async(nextProps)=>{const params={};if(nextProps.info.resId!==this.model.root.resId){params.resId=nextProps.info.resId;}
if(nextProps.values){params.values=await prepareLoadWithValues(nextProps.values);}
if(Object.keys(params).length){return this.model.load(params);}});}
getActiveFields(){if(this.props.info.activeFields){const activeFields={};for(const[fName,fInfo]of Object.entries(this.props.info.activeFields)){activeFields[fName]={...defaultActiveField,...fInfo};}
return activeFields;}
return Object.fromEntries(this.props.info.fieldNames.map((f)=>[f,{...defaultActiveField}]));}}
const Record=__exports.Record=class Record extends Component{static template=xml`<_Record fields="fields" slots="props.slots" values="props.values" info="props" />`;static components={_Record};static props=["slots","resModel?","fieldNames?","activeFields?","fields?","resId?","mode?","values?","onRecordChanged?","onRecordSaved?","onWillSaveRecord?",];setup(){if(this.props.fields){this.fields=this.props.fields;}else{const orm=useService("orm");onWillStart(async()=>{this.fields=await orm.call(this.props.resModel,"fields_get",[this.props.fieldNames],{});});}}}
return __exports;});;

/* /web/static/src/model/relational_model/datapoint.js */
odoo.define('@web/model/relational_model/datapoint',['@odoo/owl','@web/core/domain','@web/core/utils/reactive','@web/model/relational_model/utils'],function(require){'use strict';let __exports={};const{markRaw}=require("@odoo/owl");const{evalDomain}=require("@web/core/domain");const{Reactive}=require("@web/core/utils/reactive");const{getId}=require("@web/model/relational_model/utils");const DataPoint=__exports.DataPoint=class DataPoint extends Reactive{constructor(model,config,data,options){super(...arguments);this.id=getId("datapoint");this.model=model;markRaw(config.activeFields);markRaw(config.fields);this._config=config;this.setup(config,data,options);}
setup(){}
get activeFields(){return this.config.activeFields;}
get fields(){return this.config.fields;}
get fieldNames(){return Object.keys(this.activeFields).filter((fieldName)=>!this.fields[fieldName].relatedPropertyField);}
get resModel(){return this.config.resModel;}
get config(){return this._config;}
get context(){return this.config.context;}
get currentCompanyId(){return this.config.currentCompanyId;}
isFieldReadonly(fieldName){const activeField=this.activeFields[fieldName];const{readonly}=activeField||this.fields[fieldName];return readonly&&evalDomain(readonly,this.evalContext);}}
return __exports;});;

/* /web/static/src/model/relational_model/dynamic_group_list.js */
odoo.define('@web/model/relational_model/dynamic_group_list',['@web/core/domain','@web/model/relational_model/dynamic_list','@web/model/relational_model/utils'],function(require){'use strict';let __exports={};const{Domain}=require("@web/core/domain");const{DynamicList}=require("@web/model/relational_model/dynamic_list");const{getGroupServerValue}=require("@web/model/relational_model/utils");const DynamicGroupList=__exports.DynamicGroupList=class DynamicGroupList extends DynamicList{static type="DynamicGroupList";setup(config,data){super.setup(...arguments);this.isGrouped=true;this._nbRecordsMatchingDomain=null;this._setData(data);}
_setData(data){this.groups=data.groups.map((g)=>this._createGroupDatapoint(g));this.count=data.length;this._selectDomain(this.isDomainSelected);}
get groupBy(){return this.config.groupBy;}
get groupByField(){return this.fields[this.groupBy[0].split(":")[0]];}
get hasData(){return this.groups.some((group)=>group.hasData);}
get isRecordCountTrustable(){return this.count<=this.limit||this._nbRecordsMatchingDomain!==null;}
get records(){return this.groups.filter((group)=>!group.isFolded).map((group)=>group.records).flat();}
get recordCount(){if(this._nbRecordsMatchingDomain!==null){return this._nbRecordsMatchingDomain;}
return this.groups.reduce((acc,group)=>acc+group.count,0);}
async createGroup(groupName,foldField){if(!this.groupByField||this.groupByField.type!=="many2one"){throw new Error("Cannot create a group on a non many2one group field");}
await this.model.mutex.exec(()=>this._createGroup(groupName,foldField));}
async deleteGroups(groups){await this.model.mutex.exec(()=>this._deleteGroups(groups));}
async moveRecord(dataRecordId,dataGroupId,refId,targetGroupId){const targetGroup=this.groups.find((g)=>g.id===targetGroupId);if(dataGroupId===targetGroupId){await targetGroup.list._resequence(targetGroup.list.records,this.resModel,dataRecordId,refId);return;}
const sourceGroup=this.groups.find((g)=>g.id===dataGroupId);const recordIndex=sourceGroup.list.records.findIndex((r)=>r.id===dataRecordId);const record=sourceGroup.list.records[recordIndex];const refIndex=targetGroup.list.records.findIndex((r)=>r.id===refId);const oldIndex=sourceGroup.list.records.findIndex((r)=>r.id===dataRecordId);const sourceList=sourceGroup.list;const mustReloadSourceList=sourceList.count>sourceList.offset+sourceList.limit;sourceGroup._removeRecords([record.id]);targetGroup._addRecord(record,refIndex+1);const value=targetGroup.groupByField.type==="many2one"?[targetGroup.value,targetGroup.displayName]:targetGroup.value;const revert=()=>{targetGroup._removeRecords([record.id]);sourceGroup._addRecord(record,oldIndex);};try{const changes={[targetGroup.groupByField.name]:value};const res=await record.update(changes,{save:true});if(!res){return revert();}}catch(e){revert();throw e;}
const proms=[];if(mustReloadSourceList){const{offset,limit,orderBy,domain}=sourceGroup.list;proms.push(sourceGroup.list._load(offset,limit,orderBy,domain));}
if(!targetGroup.isFolded){const targetList=targetGroup.list;const records=targetList.records;proms.push(targetList._resequence(records,this.resModel,dataRecordId,refId));}
return Promise.all(proms);}
async resequence(movedGroupId,targetGroupId){if(!this.groupByField||this.groupByField.type!=="many2one"){throw new Error("Cannot resequence a group on a non many2one group field");}
return this.model.mutex.exec(async()=>{await this._resequence(this.groups,this.groupByField.relation,movedGroupId,targetGroupId);});}
async selectDomain(value){return this.model.mutex.exec(async()=>{await this._ensureCorrectRecordCount();this._selectDomain(value);});}
async sortBy(fieldName){if(!this.groups.length){return;}
if(this.groups.every((group)=>group.isFolded)){if(this.groupByField.name!==fieldName){if(!(fieldName in this.groups[0].aggregates)){return;}}}
return super.sortBy(fieldName);}
async _createGroup(groupName,foldField=false){const[id]=await this.model.orm.call(this.groupByField.relation,"name_create",[groupName],{context:this.context});if(foldField){await this.model.orm.write(this.groupByField.relation,[id],{[foldField]:true},{context:this.context});}
const lastGroup=this.groups.at(-1);const commonConfig={resModel:this.config.resModel,fields:this.config.fields,activeFields:this.config.activeFields,};const context={...this.context,[`default_${this.groupByField.name}`]:id,};const nextConfigGroups={...this.config.groups};const domain=Domain.and([this.domain,[[this.groupByField.name,"=",id]]]).toList();nextConfigGroups[id]={...commonConfig,context,groupByFieldName:this.groupByField.name,isFolded:Boolean(foldField),initialDomain:domain,list:{...commonConfig,context,domain:domain,groupBy:[],orderBy:this.orderBy,},};this.model._updateConfig(this.config,{groups:nextConfigGroups},{reload:false});const data={count:0,length:0,records:[],__domain:domain,[this.groupByField.name]:[id,groupName],value:id,serverValue:getGroupServerValue(this.groupByField,id),displayName:groupName,rawValue:[id,groupName],};const group=this._createGroupDatapoint(data);if(lastGroup){const groups=[...this.groups,group];await this._resequence(groups,this.groupByField.relation,group.id,lastGroup.id);this.groups=groups;}else{this.groups.push(group);}}
_createGroupDatapoint(data){return new this.model.constructor.Group(this.model,this.config.groups[data.value],data);}
async _deleteGroups(groups){const shouldReload=groups.some((g)=>g.count>0);const succeeded=await this._unlinkGroups(groups);if(succeeded===false){return;}
const configGroups={...this.config.groups};for(const group of groups){delete configGroups[group.value];}
if(shouldReload){await this.model._updateConfig(this.config,{groups:configGroups},{commit:this._setData.bind(this)});}else{for(const group of groups){this._removeGroup(group);}
this.model._updateConfig(this.config,{groups:configGroups},{reload:false});}}
async _ensureCorrectRecordCount(){if(!this.isRecordCountTrustable){this._nbRecordsMatchingDomain=await this.model.orm.searchCount(this.resModel,this.domain,{limit:this.model.initialCountLimit});}}
_getDPresId(group){return group.value;}
_getDPFieldValue(group,handleField){return group[handleField];}
async _load(offset,limit,orderBy,domain){await this.model._updateConfig(this.config,{offset,limit,orderBy,domain},{commit:this._setData.bind(this)});if(this.isDomainSelected){await this._ensureCorrectRecordCount();}}
_removeGroup(group){const index=this.groups.findIndex((g)=>g.id===group.id);this.groups.splice(index,1);this.count--;}
_removeRecords(recordIds){const proms=[];for(const group of this.groups){proms.push(group._removeRecords(recordIds));}
return Promise.all(proms);}
_selectDomain(value){for(const group of this.groups){group.list._selectDomain(value);}
super._selectDomain(value);}
async _toggleSelection(){if(!this.records.length){if(!this.isDomainSelected){await this._ensureCorrectRecordCount();this._selectDomain(true);}else{this._selectDomain(false);}}else{super._toggleSelection();}}
_unlinkGroups(groups){const groupResIds=groups.map((g)=>g.value);return this.model.orm.unlink(this.groupByField.relation,groupResIds,{context:this.context,});}}
return __exports;});;

/* /web/static/src/model/relational_model/dynamic_list.js */
odoo.define('@web/model/relational_model/dynamic_list',['@web/core/confirmation_dialog/confirmation_dialog','@web/core/l10n/translation','@web/core/utils/arrays','@web/model/relational_model/datapoint','@web/model/relational_model/record','@web/model/relational_model/utils'],function(require){'use strict';let __exports={};const{AlertDialog}=require("@web/core/confirmation_dialog/confirmation_dialog");const{_t}=require("@web/core/l10n/translation");const{unique}=require("@web/core/utils/arrays");const{DataPoint}=require("@web/model/relational_model/datapoint");const{Record}=require("@web/model/relational_model/record");const{resequence}=require("@web/model/relational_model/utils");const DEFAULT_HANDLE_FIELD="sequence";const DynamicList=__exports.DynamicList=class DynamicList extends DataPoint{setup(config){super.setup(...arguments);this.handleField=Object.keys(this.activeFields).find((fieldName)=>this.activeFields[fieldName].isHandle);if(!this.handleField&&DEFAULT_HANDLE_FIELD in this.fields){this.handleField=DEFAULT_HANDLE_FIELD;}
this.isDomainSelected=false;this.evalContext=this.context;}
get groupBy(){return[];}
get orderBy(){return this.config.orderBy;}
get domain(){return this.config.domain;}
get editedRecord(){return this.records.find((record)=>record.isInEdition);}
get isRecordCountTrustable(){return true;}
get limit(){return this.config.limit;}
get offset(){return this.config.offset;}
get selection(){return this.records.filter((record)=>record.selected);}
archive(isSelected){return this.model.mutex.exec(()=>this._toggleArchive(isSelected,true));}
canResequence(){return!!this.handleField;}
deleteRecords(records=[]){return this.model.mutex.exec(()=>this._deleteRecords(records));}
duplicateRecords(records=[]){return this.model.mutex.exec(()=>this._duplicateRecords(records));}
async enterEditMode(record){if(this.editedRecord===record){return true;}
const canProceed=await this.leaveEditMode();if(canProceed){this.model._updateConfig(record.config,{mode:"edit"},{reload:false});}
return canProceed;}
async getResIds(isSelected){let resIds;if(isSelected){if(this.isDomainSelected){resIds=await this.model.orm.search(this.resModel,this.domain,{limit:this.model.activeIdsLimit,context:this.context,});}else{resIds=this.selection.map((r)=>r.resId);}}else{resIds=this.records.map((r)=>r.resId);}
return unique(resIds);}
async leaveEditMode({discard}={}){let editedRecord=this.editedRecord;if(editedRecord){let canProceed=true;if(discard){this._recordToDiscard=editedRecord;await editedRecord.discard();this._recordToDiscard=null;editedRecord=this.editedRecord;if(editedRecord&&editedRecord.isNew){this._removeRecords([editedRecord.id]);}}else{if(!this.model._urgentSave){await editedRecord.checkValidity();editedRecord=this.editedRecord;if(!editedRecord){return true;}}
if(editedRecord.isNew&&!editedRecord.dirty){this._removeRecords([editedRecord.id]);}else{canProceed=await editedRecord.save();}}
editedRecord=this.editedRecord;if(canProceed&&editedRecord){this.model._updateConfig(editedRecord.config,{mode:"readonly"},{reload:false});}else{return canProceed;}}
return true;}
load(params={}){const limit=params.limit===undefined?this.limit:params.limit;const offset=params.offset===undefined?this.offset:params.offset;const orderBy=params.orderBy===undefined?this.orderBy:params.orderBy;const domain=params.domain===undefined?this.domain:params.domain;return this.model.mutex.exec(()=>this._load(offset,limit,orderBy,domain));}
async multiSave(record){return this.model.mutex.exec(()=>this._multiSave(record));}
selectDomain(value){return this.model.mutex.exec(()=>this._selectDomain(value));}
sortBy(fieldName){return this.model.mutex.exec(()=>{let orderBy=[...this.orderBy];if(orderBy.length&&orderBy[0].name===fieldName){orderBy[0]={name:orderBy[0].name,asc:!orderBy[0].asc};}else{orderBy=orderBy.filter((o)=>o.name!==fieldName);orderBy.unshift({name:fieldName,asc:true,});}
return this._load(this.offset,this.limit,orderBy,this.domain);});}
toggleSelection(){return this.model.mutex.exec(()=>this._toggleSelection());}
unarchive(isSelected){return this.model.mutex.exec(()=>this._toggleArchive(isSelected,false));}
async _duplicateRecords(records){let resIds;if(records.length){resIds=unique(records.map((r)=>r.resId));}else{resIds=await this.getResIds(true);}
const duplicated=await this.model.orm.call(this.resModel,"copy",[resIds],{context:this.context,});if(resIds.length>duplicated.length){this.model.notification.add(_t("Some records could not be duplicated"),{title:_t("Warning"),});}
return this.model.load();}
async _deleteRecords(records){let resIds;if(records.length){resIds=unique(records.map((r)=>r.resId));}else{resIds=await this.getResIds(true);records=this.records.filter((r)=>resIds.includes(r.resId));}
const unlinked=await this.model.orm.unlink(this.resModel,resIds,{context:this.context,});if(!unlinked){return false;}
if(this.isDomainSelected&&resIds.length===this.model.activeIdsLimit&&resIds.length<this.count){const msg=_t("Only the first %(count)s records have been deleted (out of %(total)s selected)",{count:resIds.length,total:this.count});this.model.notification.add(msg,{title:_t("Warning")});}
await this.model.load();return unlinked;}
async _leaveSampleMode(){if(this.model.useSampleModel){await this._load(this.offset,this.limit,this.orderBy,this.domain);this.model.useSampleModel=false;}}
async _multiSave(record){const changes=record._getChanges();if(!Object.keys(changes).length||record===this._recordToDiscard){return;}
const validSelection=this.selection.filter((record)=>{return Object.keys(changes).every((fieldName)=>{if(record._isReadonly(fieldName)){return false;}else if(record._isRequired(fieldName)&&!changes[fieldName]){return false;}
return true;});});const canProceed=await this.model.hooks.onWillSaveMulti(record,changes,validSelection);if(canProceed===false){return false;}
if(validSelection.length===0){this.model.dialog.add(AlertDialog,{body:_t("No valid record to save"),confirm:()=>this.leaveEditMode({discard:true}),dismiss:()=>this.leaveEditMode({discard:true}),});return false;}else{const resIds=unique(validSelection.map((r)=>r.resId));const context=this.context;try{await this.model.orm.write(this.resModel,resIds,changes,{context});}catch(e){record._discard();this.model._updateConfig(record.config,{mode:"readonly"},{reload:false});throw e;}
const records=await this.model._loadRecords({...this.config,resIds});for(const record of validSelection){const serverValues=records.find((r)=>r.id===record.resId);record._applyValues(serverValues);this.model._updateSimilarRecords(record,serverValues);}
record._discard();this.model._updateConfig(record.config,{mode:"readonly"},{reload:false});}
this.model.hooks.onSavedMulti(validSelection);return true;}
async _resequence(originalList,resModel,movedId,targetId){if(this.resModel===resModel&&!this.canResequence()){return;}
const handleField=this.resModel===resModel?this.handleField:DEFAULT_HANDLE_FIELD;const order=this.orderBy.find((o)=>o.name===handleField);const getSequence=(dp)=>dp&&this._getDPFieldValue(dp,handleField);const getResId=(dp)=>this._getDPresId(dp);const resequencedRecords=await resequence({records:originalList,resModel,movedId,targetId,fieldName:handleField,asc:order?.asc,context:this.context,orm:this.model.orm,getSequence,getResId,});if(resequencedRecords){for(const dpData of resequencedRecords){const dp=originalList.find((d)=>getResId(d)===dpData.id);if(dp instanceof Record){dp._applyValues(dpData);}else{dp[handleField]=dpData[handleField];}}}}
_selectDomain(value){this.isDomainSelected=value;}
async _toggleArchive(isSelected,state){const method=state?"action_archive":"action_unarchive";const context=this.context;const resIds=await this.getResIds(isSelected);const action=await this.model.orm.call(this.resModel,method,[resIds],{context});if(this.isDomainSelected&&resIds.length===this.model.activeIdsLimit&&resIds.length<this.count){const msg=_t("Of the %(selectedRecord)s selected records, only the first %(firstRecords)s have been archived/unarchived.",{selectedRecords:resIds.length,firstRecords:this.count,});this.model.notification.add(msg,{title:_t("Warning")});}
const reload=()=>this.model.load();if(action&&Object.keys(action).length){this.model.action.doAction(action,{onClose:reload,});}else{return reload();}}
async _toggleSelection(){if(this.selection.length===this.records.length){this.records.forEach((record)=>{record._toggleSelection(false);});this._selectDomain(false);}else{this.records.forEach((record)=>{record._toggleSelection(true);});}}}
return __exports;});;

/* /web/static/src/model/relational_model/dynamic_record_list.js */
odoo.define('@web/model/relational_model/dynamic_record_list',['@web/model/relational_model/dynamic_list'],function(require){'use strict';let __exports={};const{DynamicList}=require("@web/model/relational_model/dynamic_list");const DynamicRecordList=__exports.DynamicRecordList=class DynamicRecordList extends DynamicList{static type="DynamicRecordList";setup(config,data){super.setup(config);this._setData(data);}
_setData(data){this.records=data.records.map((r)=>this._createRecordDatapoint(r));this._updateCount(data);this._selectDomain(this.isDomainSelected);}
get hasData(){return this.count>0;}
addExistingRecord(resId,atFirstPosition){return this.model.mutex.exec(async()=>{const record=this._createRecordDatapoint({});await record._load({resId});this._addRecord(record,atFirstPosition?0:this.records.length);return record;});}
addNewRecord(atFirstPosition=false){return this.model.mutex.exec(async()=>{await this._leaveSampleMode();return this._addNewRecord(atFirstPosition);});}
async fetchCount(){this.count=await this.model._updateCount(this.config);this.hasLimitedCount=false;return this.count;}
moveRecord(dataRecordId,_dataGroupId,refId,_targetGroupId){return this.resequence(dataRecordId,refId);}
removeRecord(record){if(!record.isNew){throw new Error("removeRecord can't be called on an existing record");}
const index=this.records.findIndex((r)=>r===record);if(index<0){return;}
this.records.splice(index,1);this.count--;return record;}
async resequence(movedRecordId,targetRecordId){return this.model.mutex.exec(async()=>await this._resequence(this.records,this.resModel,movedRecordId,targetRecordId));}
async _addNewRecord(atFirstPosition){const values=await this.model._loadNewRecord({resModel:this.resModel,activeFields:this.activeFields,fields:this.fields,context:this.context,});const record=this._createRecordDatapoint(values,"edit");this._addRecord(record,atFirstPosition?0:this.records.length);return record;}
_addRecord(record,index){this.records.splice(Number.isInteger(index)?index:this.records.length,0,record);this.count++;}
_createRecordDatapoint(data,mode="readonly"){return new this.model.constructor.Record(this.model,{context:this.context,activeFields:this.activeFields,resModel:this.resModel,fields:this.fields,resId:data.id||false,resIds:data.id?[data.id]:[],isMonoRecord:true,currentCompanyId:this.currentCompanyId,mode,},data,{manuallyAdded:!data.id});}
_getDPresId(record){return record.resId;}
_getDPFieldValue(record,handleField){return record.data[handleField];}
async _load(offset,limit,orderBy,domain){await this.model._updateConfig(this.config,{offset,limit,orderBy,domain},{commit:this._setData.bind(this)});}
_removeRecords(recordIds){const keptRecords=this.records.filter((r)=>!recordIds.includes(r.id));this.count-=this.records.length-keptRecords.length;this.records=keptRecords;if(this.offset&&!this.records.length){const offset=Math.max(this.offset-this.limit,0);this.model._updateConfig(this.config,{offset},{reload:false});}}
_selectDomain(value){if(value){this.records.forEach((r)=>(r.selected=true));}
super._selectDomain(value);}
_updateCount(data){const length=data.length;if(length>=this.config.countLimit+1){this.hasLimitedCount=true;this.count=this.config.countLimit;}else{this.hasLimitedCount=false;this.count=length;}}}
return __exports;});;

/* /web/static/src/model/relational_model/errors.js */
odoo.define('@web/model/relational_model/errors',['@web/core/registry','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{registry}=require("@web/core/registry");const{_t}=require("@web/core/l10n/translation");const FetchRecordError=__exports.FetchRecordError=class FetchRecordError extends Error{constructor(resIds){super(_t("It seems the records with IDs %s cannot be found. They might have been deleted.",resIds));this.resIds=resIds;}}
function fetchRecordErrorHandler(env,error,originalError){if(originalError instanceof FetchRecordError){env.services.notification.add(originalError.message,{sticky:true,type:"danger"});return true;}}
const errorHandlerRegistry=registry.category("error_handlers");errorHandlerRegistry.add("fetchRecordErrorHandler",fetchRecordErrorHandler);return __exports;});;

/* /web/static/src/model/relational_model/group.js */
odoo.define('@web/model/relational_model/group',['@web/core/domain','@web/model/relational_model/datapoint'],function(require){'use strict';let __exports={};const{Domain}=require("@web/core/domain");const{DataPoint}=require("@web/model/relational_model/datapoint");const Group=__exports.Group=class Group extends DataPoint{static type="Group";setup(config,data){super.setup(...arguments);this.groupByField=this.fields[config.groupByFieldName];this.range=data.range;this._rawValue=data.rawValue;this.count=data.count;this.value=data.value;this.serverValue=data.serverValue;this.displayName=data.displayName;this.aggregates=data.aggregates;let List;if(config.list.groupBy.length){List=this.model.constructor.DynamicGroupList;}else{List=this.model.constructor.DynamicRecordList;}
this.list=new List(this.model,config.list,data);this._useGroupCountForList();if(config.record){config.record.context={...config.record.context,...config.context};this.record=new this.model.constructor.Record(this.model,config.record,data.values);}}
get groupDomain(){return this.config.initialDomain;}
get hasData(){return this.count>0;}
get isFolded(){return this.config.isFolded;}
get records(){return this.list.records;}
async addExistingRecord(resId,atFirstPosition=false){const record=await this.list.addExistingRecord(resId,atFirstPosition);this.count++;return record;}
async addNewRecord(_unused,atFirstPosition=false){const canProceed=await this.model.root.leaveEditMode();if(canProceed){const record=await this.list.addNewRecord(atFirstPosition);if(record){this.count++;}}}
async applyFilter(filter){if(filter){await this.list.load({domain:Domain.and([this.groupDomain,filter]).toList(),});}else{await this.list.load({domain:this.groupDomain});this.count=this.list.isGrouped?this.list.recordCount:this.list.count;}
this.model._updateConfig(this.config,{extraDomain:filter},{reload:false});}
deleteRecords(records){return this.model.mutex.exec(()=>this._deleteRecords(records));}
async toggle(){if(this.config.isFolded){await this.list.load();}
this._useGroupCountForList();this.model._updateConfig(this.config,{isFolded:!this.config.isFolded},{reload:false});}
_addRecord(record,index){this.list._addRecord(record,index);this.count++;}
async _deleteRecords(records){await this.list._deleteRecords(records);this.count-=records.length;}
_useGroupCountForList(){if(!this.list.isGrouped&&this.list.count===this.list.config.countLimit){this.list.count=this.count;}}
async _removeRecords(recordIds){const idsToRemove=recordIds.filter((id)=>this.list.records.some((r)=>r.id===id));this.list._removeRecords(idsToRemove);this.count-=idsToRemove.length;}}
return __exports;});;

/* /web/static/src/model/relational_model/record.js */
odoo.define('@web/model/relational_model/record',['@odoo/owl','@web/core/confirmation_dialog/confirmation_dialog','@web/core/l10n/dates','@web/core/l10n/translation','@web/core/orm_service','@web/core/py_js/py','@web/core/utils/strings','@web/model/relational_model/datapoint','@web/model/relational_model/utils','@web/model/relational_model/errors','@web/core/network/rpc'],function(require){'use strict';let __exports={};const{markRaw,markup,toRaw}=require("@odoo/owl");const{AlertDialog}=require("@web/core/confirmation_dialog/confirmation_dialog");const{serializeDate,serializeDateTime}=require("@web/core/l10n/dates");const{_t}=require("@web/core/l10n/translation");const{x2ManyCommands}=require("@web/core/orm_service");const{evaluateBooleanExpr}=require("@web/core/py_js/py");const{escape}=require("@web/core/utils/strings");const{DataPoint}=require("@web/model/relational_model/datapoint");const{createPropertyActiveField,getBasicEvalContext,getFieldContext,getFieldsSpec,parseServerValue,}=require("@web/model/relational_model/utils");const{FetchRecordError}=require("@web/model/relational_model/errors");const{RequestEntityTooLargeError}=require("@web/core/network/rpc");const Record=__exports.Record=class Record extends DataPoint{static type="Record";setup(config,data,options={}){this._manuallyAdded=options.manuallyAdded===true;this._onUpdate=options.onUpdate||(()=>{});this._parentRecord=options.parentRecord;this.canSaveOnUpdate=!options.parentRecord;this._virtualId=options.virtualId||false;this._isEvalContextReady=false;this.dirty=false;this.selected=false;this._invalidFields=new Set();this._unsetRequiredFields=markRaw(new Set());this._closeInvalidFieldsNotification=()=>{};const parentRecord=this._parentRecord;if(parentRecord){this.evalContext={get parent(){return parentRecord.evalContext;},};this.evalContextWithVirtualIds={get parent(){return parentRecord.evalContextWithVirtualIds;},};}else{this.evalContext={};this.evalContextWithVirtualIds={};}
const missingFields=this.fieldNames.filter((fieldName)=>!(fieldName in data));data={...this._getDefaultValues(missingFields),...data};this._textValues=markRaw({});this._setData(data);}
_setData(data){this._isEvalContextReady=false;if(this.resId){this._values=this._parseServerValues(data);this._changes=markRaw({});Object.assign(this._textValues,this._getTextValues(data));}else{this._values=markRaw({});const allVals={...this._getDefaultValues(),...data};this._initialChanges=markRaw(this._parseServerValues(allVals));this._changes=markRaw({...this._initialChanges});Object.assign(this._textValues,this._getTextValues(allVals));}
this.dirty=false;this.data={...this._values,...this._changes};this._setEvalContext();this._initialTextValues={...this._textValues};this._invalidFields.clear();this._savePoint=undefined;}
get canBeAbandoned(){return this.isNew&&!this.dirty&&this._manuallyAdded;}
get hasData(){return true;}
get isActive(){if("active"in this.activeFields){return this.data.active;}else if("x_active"in this.activeFields){return this.data.x_active;}
return true;}
get isInEdition(){if(this.config.mode==="readonly"){return false;}else{return this.config.mode==="edit"||!this.resId;}}
get isNew(){return!this.resId;}
get isValid(){return!this._invalidFields.size;}
get resId(){return this.config.resId;}
get resIds(){return this.config.resIds;}
archive(){return this.model.mutex.exec(()=>this._toggleArchive(true));}
async checkValidity({displayNotification}={}){if(!this._urgentSave){await this.model._askChanges();}
return this._checkValidity({displayNotification});}
delete(){return this.model.mutex.exec(async()=>{const unlinked=await this.model.orm.unlink(this.resModel,[this.resId],{context:this.context,});if(!unlinked){return false;}
const resIds=this.resIds.slice();const index=resIds.indexOf(this.resId);resIds.splice(index,1);const resId=resIds[Math.min(index,resIds.length-1)]||false;if(resId){await this.model.load({resId,resIds});}else{this.model._updateConfig(this.config,{resId:false},{reload:false});this.dirty=false;this._changes=markRaw(this._parseServerValues(this._getDefaultValues()));this._values=markRaw({});this._textValues=markRaw({});this.data={...this._changes};this._setEvalContext();}});}
async discard(){if(this.model._closeUrgentSaveNotification){this.model._closeUrgentSaveNotification();}
await this.model._askChanges();return this.model.mutex.exec(()=>this._discard());}
duplicate(){return this.model.mutex.exec(async()=>{const kwargs={context:this.context};const index=this.resIds.indexOf(this.resId);const[resId]=await this.model.orm.call(this.resModel,"copy",[[this.resId]],kwargs);const resIds=this.resIds.slice();resIds.splice(index+1,0,resId);await this.model.load({resId,resIds,mode:"edit"});});}
async isDirty(){await this.model._askChanges();return this.dirty;}
isFieldInvalid(fieldName){return this._invalidFields.has(fieldName);}
load(){if(arguments.length>0){throw new Error("Record.load() does not accept arguments");}
return this.model.mutex.exec(()=>this._load());}
async save(options){await this.model._askChanges();return this.model.mutex.exec(()=>this._save(options));}
async setInvalidField(fieldName){this.dirty=true;return this._setInvalidField(fieldName);}
async resetFieldValidity(fieldName){this.dirty=true;return this._resetFieldValidity(fieldName);}
switchMode(mode){return this.model.mutex.exec(()=>this._switchMode(mode));}
toggleSelection(selected){return this.model.mutex.exec(()=>{this._toggleSelection(selected);});}
unarchive(){return this.model.mutex.exec(()=>this._toggleArchive(false));}
update(changes,{save}={}){if(this.model._urgentSave){return this._update(changes);}
return this.model.mutex.exec(async()=>{await this._update(changes,{withoutOnchange:save});if(save&&this.canSaveOnUpdate){return this._save();}});}
async urgentSave(){this.model._urgentSave=true;this.model.bus.trigger("WILL_SAVE_URGENTLY");const succeeded=await this._save({reload:false});this.model._urgentSave=false;return succeeded;}
_addSavePoint(){this._savePoint=markRaw({dirty:this.dirty,textValues:{...this._textValues},changes:{...this._changes},});for(const fieldName in this._changes){if(["one2many","many2many"].includes(this.fields[fieldName].type)){this._changes[fieldName]._addSavePoint();}}}
_applyChanges(changes,serverChanges={}){const initialTextValues={...this._textValues};const initialChanges={...this._changes};const initialData={...toRaw(this.data)};const invalidFields=[...toRaw(this._invalidFields)];const undoChanges=()=>{for(const fieldName of invalidFields){this.setInvalidField(fieldName);}
Object.assign(this.data,initialData);this._changes=markRaw(initialChanges);Object.assign(this._textValues,initialTextValues);this._setEvalContext();};for(const fieldName in changes){const change=changes[fieldName];this._changes[fieldName]=change;this.data[fieldName]=change;if(this.fields[fieldName].type==="html"){this._textValues[fieldName]=change===false?false:change.toString();}else if(["char","text"].includes(this.fields[fieldName].type)){this._textValues[fieldName]=change;}}
const parsedChanges=this._parseServerValues(serverChanges,this.data);for(const fieldName in parsedChanges){this._changes[fieldName]=parsedChanges[fieldName];this.data[fieldName]=parsedChanges[fieldName];}
Object.assign(this._textValues,this._getTextValues(serverChanges));this._setEvalContext();this._removeInvalidFields(Object.keys({...changes,...serverChanges}));this._checkValidity({removeInvalidOnly:true});return undoChanges;}
_applyDefaultValues(){const fieldNames=this.fieldNames.filter((fieldName)=>{return!(fieldName in this.data);});const defaultValues=this._getDefaultValues(fieldNames);if(this.isNew){this._applyChanges({},defaultValues);}else{this._applyValues(defaultValues);}}
_applyValues(values){const newValues=this._parseServerValues(values);Object.assign(this._values,newValues);for(const fieldName in newValues){if(fieldName in this._changes){if(["one2many","many2many"].includes(this.fields[fieldName].type)){this._changes[fieldName]=newValues[fieldName];}}}
Object.assign(this.data,this._values,this._changes);const textValues=this._getTextValues(values);Object.assign(this._initialTextValues,textValues);Object.assign(this._textValues,textValues,this._getTextValues(this._changes));this._setEvalContext();}
_checkValidity({silent,displayNotification,removeInvalidOnly}={}){const unsetRequiredFields=new Set();for(const fieldName in this.activeFields){const fieldType=this.fields[fieldName].type;if(this._isInvisible(fieldName)||this.fields[fieldName].relatedPropertyField){continue;}
switch(fieldType){case"boolean":case"float":case"integer":case"monetary":continue;case"html":if(this._isRequired(fieldName)&&this.data[fieldName].length===0){unsetRequiredFields.add(fieldName);}
break;case"one2many":case"many2many":{const list=this.data[fieldName];if((this._isRequired(fieldName)&&!list.count)||!list.records.every((r)=>!r.dirty||r._checkValidity({silent,removeInvalidOnly}))){unsetRequiredFields.add(fieldName);}
break;}
case"properties":{const value=this.data[fieldName];if(value){const ok=value.every((propertyDefinition)=>propertyDefinition.name&&propertyDefinition.name.length&&propertyDefinition.string&&propertyDefinition.string.length);if(!ok){unsetRequiredFields.add(fieldName);}}
break;}
case"json":{if(this._isRequired(fieldName)&&(!this.data[fieldName]||!Object.keys(this.data[fieldName]).length)){unsetRequiredFields.add(fieldName);}
break;}
default:if(!this.data[fieldName]&&this._isRequired(fieldName)){unsetRequiredFields.add(fieldName);}}}
if(silent){return!unsetRequiredFields.size;}
if(removeInvalidOnly){for(const fieldName of Array.from(this._unsetRequiredFields)){if(!unsetRequiredFields.has(fieldName)){this._unsetRequiredFields.delete(fieldName);this._invalidFields.delete(fieldName);}}}else{for(const fieldName of Array.from(this._unsetRequiredFields)){this._invalidFields.delete(fieldName);}
this._unsetRequiredFields.clear();for(const fieldName of unsetRequiredFields){this._unsetRequiredFields.add(fieldName);this._setInvalidField(fieldName);}}
const isValid=!this._invalidFields.size;if(!isValid&&displayNotification){const items=[...this._invalidFields].map((fieldName)=>{return`<li>${escape(this.fields[fieldName].string || fieldName)}</li>`;},this);this._closeInvalidFieldsNotification=this.model.notification.add(markup(`<ul>${items.join("")}</ul>`),{title:_t("Invalid fields: "),type:"danger",});}
return isValid;}
async _completeMany2OneValue(value,fieldName,resModel){const resId=value[0];const displayName=value[1];if(!resId&&!displayName){return false;}
const context=getFieldContext(this,fieldName);if(!resId&&displayName!==undefined){return this.model.orm.call(resModel,"name_create",[displayName],{context});}
if(resId&&displayName===undefined){const kwargs={context,specification:{display_name:{}},};const records=await this.model.orm.webRead(resModel,[resId],kwargs);return[resId,records[0].display_name];}
return value;}
_computeDataContext(){const dataContext={};const x2manyDataContext={withVirtualIds:{},withoutVirtualIds:{},};const data=toRaw(this.data);for(const fieldName in data){const value=data[fieldName];const field=this.fields[fieldName];if(field.relatedPropertyField){continue;}
if(["char","text","html"].includes(field.type)){dataContext[fieldName]=this._textValues[fieldName];}else if(field.type==="one2many"||field.type==="many2many"){x2manyDataContext.withVirtualIds[fieldName]=value.currentIds;x2manyDataContext.withoutVirtualIds[fieldName]=value.currentIds.filter((id)=>typeof id==="number");}else if(value&&field.type==="date"){dataContext[fieldName]=serializeDate(value);}else if(value&&field.type==="datetime"){dataContext[fieldName]=serializeDateTime(value);}else if(value&&field.type==="many2one"){dataContext[fieldName]=value[0];}else if(value&&field.type==="reference"){dataContext[fieldName]=`${value.resModel},${value.resId}`;}else if(field.type==="properties"){dataContext[fieldName]=value.filter((property)=>!property.definition_deleted!==false);}else{dataContext[fieldName]=value;}}
dataContext.id=this.resId||false;return{withVirtualIds:{...dataContext,...x2manyDataContext.withVirtualIds},withoutVirtualIds:{...dataContext,...x2manyDataContext.withoutVirtualIds},};}
_createStaticListDatapoint(data,fieldName){const{related,limit,defaultOrderBy}=this.activeFields[fieldName];const config={resModel:this.fields[fieldName].relation,activeFields:(related&&related.activeFields)||{},fields:(related&&related.fields)||{},relationField:this.fields[fieldName].relation_field||false,offset:0,resIds:data.map((r)=>r.id),orderBy:defaultOrderBy||[],limit:limit||Number.MAX_SAFE_INTEGER,currentCompanyId:this.currentCompanyId,context:{},};const options={onUpdate:({withoutOnchange}={})=>this._update({[fieldName]:[]},{withoutOnchange}),parent:this,};return new this.model.constructor.StaticList(this.model,config,data,options);}
_discard(){for(const fieldName in this._changes){if(["one2many","many2many"].includes(this.fields[fieldName].type)){this._changes[fieldName]._discard();}}
if(this._savePoint){this.dirty=this._savePoint.dirty;this._changes=markRaw({...this._savePoint.changes});this._textValues=markRaw({...this._savePoint.textValues});}else{this.dirty=false;this._changes=markRaw(this.isNew?{...this._initialChanges}:{});this._textValues=markRaw({...this._initialTextValues});}
this.data={...this._values,...this._changes};this._savePoint=undefined;this._setEvalContext();this._invalidFields.clear();this._closeInvalidFieldsNotification();this._closeInvalidFieldsNotification=()=>{};this._restoreActiveFields();}
_formatServerValue(fieldType,value){if(fieldType==="date"){return value?serializeDate(value):false;}else if(fieldType==="datetime"){return value?serializeDateTime(value):false;}else if(fieldType==="char"||fieldType==="text"){return value!==""?value:false;}else if(fieldType==="html"){return value&&value.length?value:false;}else if(fieldType==="many2one"){return value?value[0]:false;}else if(fieldType==="many2one_reference"){return value?value.resId:0;}else if(fieldType==="reference"){return value&&value.resModel&&value.resId?`${value.resModel},${value.resId}`:false;}else if(fieldType==="properties"){return value.map((property)=>{let value;if(property.type==="many2one"){value=property.value;}else if((property.type==="date"||property.type==="datetime")&&typeof property.value==="string"){value=property.value;}else{value=this._formatServerValue(property.type,property.value);}
return{...property,value,};});}
return value;}
_getChanges(changes=this._changes,{withReadonly}={}){const result={};for(const[fieldName,value]of Object.entries(changes)){const field=this.fields[fieldName];if(fieldName==="id"){continue;}
if(!withReadonly&&fieldName in this.activeFields&&this._isReadonly(fieldName)&&!this.activeFields[fieldName].forceSave){continue;}
if(field.relatedPropertyField){continue;}
if(field.type==="one2many"||field.type==="many2many"){const commands=value._getCommands({withReadonly});if(!this.isNew&&!commands.length&&!withReadonly){continue;}
result[fieldName]=commands;}else{result[fieldName]=this._formatServerValue(field.type,value);}}
return result;}
_getDefaultValues(fieldNames=this.fieldNames){const defaultValues={};for(const fieldName of fieldNames){switch(this.fields[fieldName].type){case"integer":case"float":case"monetary":defaultValues[fieldName]=fieldName==="id"?false:0;break;case"one2many":case"many2many":defaultValues[fieldName]=[];break;default:defaultValues[fieldName]=false;}}
return defaultValues;}
_getTextValues(values){const textValues={};for(const fieldName in values){if(!this.activeFields[fieldName]){continue;}
if(["char","text","html"].includes(this.fields[fieldName].type)){textValues[fieldName]=values[fieldName];}}
return textValues;}
_isInvisible(fieldName){const invisible=this.activeFields[fieldName].invisible;return invisible?evaluateBooleanExpr(invisible,this.evalContextWithVirtualIds):false;}
_isReadonly(fieldName){const readonly=this.activeFields[fieldName].readonly;return readonly?evaluateBooleanExpr(readonly,this.evalContextWithVirtualIds):false;}
_isRequired(fieldName){const required=this.activeFields[fieldName].required;return required?evaluateBooleanExpr(required,this.evalContextWithVirtualIds):false;}
async _load(nextConfig={}){if("resId"in nextConfig&&this.resId){throw new Error("Cannot change resId of a record");}
await this.model._updateConfig(this.config,nextConfig,{commit:(values)=>{if(this.resId){this.model._updateSimilarRecords(this,values);}
this._setData(values);},});}
_processProperties(properties,fieldName,parent,currentValues={}){const data={};const hasCurrentValues=Object.keys(currentValues).length>0;for(const property of properties){const propertyFieldName=`${fieldName}.${property.name}`;if(hasCurrentValues||!this.fields[propertyFieldName]){this.fields[propertyFieldName]={...property,name:propertyFieldName,relatedPropertyField:{name:fieldName,},propertyName:property.name,relation:property.comodel,};}
if(hasCurrentValues||!this.activeFields[propertyFieldName]){this.activeFields[propertyFieldName]=createPropertyActiveField(property);}
if(!this.activeFields[propertyFieldName].relatedPropertyField){this.activeFields[propertyFieldName].relatedPropertyField={name:fieldName,id:parent?.id,displayName:parent?.display_name,};}
if(property.type==="many2many"){let staticList=currentValues[propertyFieldName];if(!staticList){staticList=this._createStaticListDatapoint((property.value||[]).map((record)=>({id:record[0],display_name:record[1],})),propertyFieldName);}
data[propertyFieldName]=staticList;}else if(property.type==="many2one"){data[propertyFieldName]=property.value.length&&property.value[1]===null?[property.value[0],_t("No Access")]:property.value;}else{data[propertyFieldName]=property.value??false;}}
return data;}
_parseServerValues(serverValues,currentValues={}){const parsedValues={};if(!serverValues){return parsedValues;}
for(const fieldName in serverValues){const value=serverValues[fieldName];if(!this.activeFields[fieldName]){continue;}
const field=this.fields[fieldName];if(field.type==="one2many"||field.type==="many2many"){let staticList=currentValues[fieldName];let valueIsCommandList=true;valueIsCommandList=value.length>0&&Array.isArray(value[0]);if(!staticList){let data=valueIsCommandList?[]:value;if(data.length>0&&typeof data[0]==="number"){data=data.map((resId)=>{return{id:resId};});}
staticList=this._createStaticListDatapoint(data,fieldName);if(valueIsCommandList){staticList._applyInitialCommands(value);}}else if(valueIsCommandList){staticList._applyCommands(value);}
parsedValues[fieldName]=staticList;}else{parsedValues[fieldName]=parseServerValue(field,value);if(field.type==="properties"){const parent=serverValues[field.definition_record];Object.assign(parsedValues,this._processProperties(parsedValues[fieldName],fieldName,parent,currentValues));}}}
return parsedValues;}
async _preprocessMany2oneChanges(changes){const proms=Object.entries(changes).filter(([fieldName])=>this.fields[fieldName].type==="many2one").map(async([fieldName,value])=>{if(!value){changes[fieldName]=false;}else if(!this.activeFields[fieldName]){changes[fieldName]=value;}else{const relation=this.fields[fieldName].relation;return this._completeMany2OneValue(value,fieldName,relation).then((v)=>{changes[fieldName]=v;});}});return Promise.all(proms);}
async _preprocessMany2OneReferenceChanges(changes){const proms=Object.entries(changes).filter(([fieldName])=>this.fields[fieldName].type==="many2one_reference").map(async([fieldName,value])=>{if(!value){changes[fieldName]=false;}else if(typeof value==="number"){changes[fieldName]={resId:value};}else{const relation=this.data[this.fields[fieldName].model_field];return this._completeMany2OneValue([value.resId,value.displayName],fieldName,relation).then((v)=>{changes[fieldName]={resId:v[0],displayName:v[1]};});}});return Promise.all(proms);}
async _preprocessReferenceChanges(changes){const proms=Object.entries(changes).filter(([fieldName])=>this.fields[fieldName].type==="reference").map(async([fieldName,value])=>{if(!value){changes[fieldName]=false;}else{return this._completeMany2OneValue([value.resId,value.displayName],fieldName,value.resModel).then((v)=>{changes[fieldName]={resId:v[0],resModel:value.resModel,displayName:v[1],};});}});return Promise.all(proms);}
async _preprocessX2manyChanges(changes){for(const[fieldName,value]of Object.entries(changes)){if(this.fields[fieldName].type!=="one2many"&&this.fields[fieldName].type!=="many2many"){continue;}
const list=this.data[fieldName];for(const command of value){switch(command[0]){case x2ManyCommands.SET:await list._replaceWith(command[2]);break;default:await list._applyCommands([command]);}}
changes[fieldName]=list;}}
_preprocessPropertiesChanges(changes){for(const[fieldName,value]of Object.entries(changes)){const field=this.fields[fieldName];if(field.type==="properties"){const parent=changes[field.definition_record]||this.data[field.definition_record];Object.assign(changes,this._processProperties(value,fieldName,parent,this.data));}else if(field&&field.relatedPropertyField){const[propertyFieldName,propertyName]=field.name.split(".");const propertiesData=this.data[propertyFieldName]||[];if(!propertiesData.find((property)=>property.name===propertyName)){this.model.notification.add(_t("This record belongs to a different parent so you can not change this property."),{type:"warning"});return;}
changes[propertyFieldName]=propertiesData.map((property)=>property.name===propertyName?{...property,value}:property);}}}
_preprocessHtmlChanges(changes){for(const[fieldName,value]of Object.entries(changes)){if(this.fields[fieldName].type==="html"){changes[fieldName]=value===false?false:markup(value);}}}
_removeInvalidFields(fieldNames){for(const fieldName of fieldNames){this._invalidFields.delete(fieldName);}}
_restoreActiveFields(){if(!this._activeFieldsToRestore){return;}
this.model._updateConfig(this.config,{activeFields:{...this._activeFieldsToRestore},},{reload:false});this._activeFieldsToRestore=undefined;}
async _save({reload=true,onError,nextId}={}){if(this.model._closeUrgentSaveNotification){this.model._closeUrgentSaveNotification();}
const creation=!this.resId;if(nextId){if(creation){throw new Error("Cannot set nextId on a new record");}
reload=true;}
for(const fieldName in this.activeFields){const field=this.fields[fieldName];if(["one2many","many2many"].includes(field.type)&&!field.relatedPropertyField){this.data[fieldName]._abandonRecords();}}
if(!this._checkValidity({displayNotification:true})){return false;}
const changes=this._getChanges();delete changes.id;if(!creation&&!Object.keys(changes).length){if(nextId){return this.model.load({resId:nextId});}
this._changes=markRaw({});this.data={...this._values};this.dirty=false;return true;}
if(this.model._urgentSave&&this.model.useSendBeaconToSaveUrgently&&!this.model.env.inDialog){const route=`/web/dataset/call_kw/${this.resModel}/web_save`;const params={model:this.resModel,method:"web_save",args:[this.resId?[this.resId]:[],changes],kwargs:{context:this.context,specification:{}},};const data={jsonrpc:"2.0",method:"call",params};const blob=new Blob([JSON.stringify(data)],{type:"application/json"});const succeeded=navigator.sendBeacon(route,blob);if(succeeded){this._changes=markRaw({});this.dirty=false;}else{this.model._closeUrgentSaveNotification=this.model.notification.add(markup(_t(`Heads up! Your recent changes are too large to save automatically. Please click the <i class="fa fa-cloud-upload fa-fw"></i> button now to ensure your work is saved before you exit this tab.`)),{sticky:true});}
return succeeded;}
const canProceed=await this.model.hooks.onWillSaveRecord(this,changes);if(canProceed===false){return false;}
let fieldSpec={};if(reload){fieldSpec=getFieldsSpec(this.activeFields,this.fields,getBasicEvalContext(this.config));}
const kwargs={context:this.context,specification:fieldSpec,next_id:nextId,};let records=[];try{records=await this.model.orm.webSave(this.resModel,this.resId?[this.resId]:[],changes,kwargs);}catch(e){if(onError&&!(e instanceof RequestEntityTooLargeError)){return onError(e,{discard:()=>this._discard()});}
if(!this.isInEdition){await this._load({});}
throw e;}
if(reload&&!records.length){throw new FetchRecordError([nextId||this.resId]);}
if(creation){const resId=records[0].id;const resIds=this.resIds.concat([resId]);this.model._updateConfig(this.config,{resId,resIds},{reload:false});}
await this.model.hooks.onRecordSaved(this,changes);if(reload){if(this.resId){this.model._updateSimilarRecords(this,records[0]);}
if(nextId){this.model._updateConfig(this.config,{resId:nextId},{reload:false});}
if(this.config.isRoot){this.model.hooks.onWillLoadRoot(this.config);}
this._setData(records[0]);}else{this._values=markRaw({...this._values,...this._changes});if("id"in this.activeFields){this._values.id=records[0].id;}
for(const fieldName in this.activeFields){const field=this.fields[fieldName];if(["one2many","many2many"].includes(field.type)&&!field.relatedPropertyField){this._changes[fieldName]?._clearCommands();}}
this._changes=markRaw({});this.data={...this._values};this.dirty=false;}
return true;}
_setEvalContext(){const evalContext=getBasicEvalContext(this.config);const dataContext=this._computeDataContext();Object.assign(this.evalContext,evalContext,dataContext.withoutVirtualIds);Object.assign(this.evalContextWithVirtualIds,evalContext,dataContext.withVirtualIds);this._isEvalContextReady=true;if(!this._parentRecord||this._parentRecord._isEvalContextReady){for(const[fieldName,value]of Object.entries(toRaw(this.data))){if(["one2many","many2many"].includes(this.fields[fieldName].type)){value._updateContext(getFieldContext(this,fieldName));}}}}
async _setInvalidField(fieldName){const canProceed=this.model.hooks.onWillSetInvalidField(this,fieldName);if(canProceed===false){return;}
if(this.selected&&this.model.multiEdit&&this.model.root._recordToDiscard!==this&&!this._invalidFields.has(fieldName)){await this.model.dialog.add(AlertDialog,{body:_t("No valid record to save"),confirm:async()=>{await this.discard();this.switchMode("readonly");},});}
this._invalidFields.add(fieldName);}
_resetFieldValidity(fieldName){this._invalidFields.delete(fieldName);}
_switchMode(mode){this.model._updateConfig(this.config,{mode},{reload:false});if(mode==="readonly"){this._noUpdateParent=false;this._invalidFields.clear();}}
async _toggleArchive(state){const method=state?"action_archive":"action_unarchive";const action=await this.model.orm.call(this.resModel,method,[[this.resId]],{context:this.context,});if(action&&Object.keys(action).length){this.model.action.doAction(action,{onClose:()=>this._load()});}else{return this._load();}}
_toggleSelection(selected){if(typeof selected==="boolean"){this.selected=selected;}else{this.selected=!this.selected;}
if(!this.selected&&this.model.root.isDomainSelected){this.model.root._selectDomain(false);}}
async _getOnchangeValues(changes){const onChangeFields=Object.keys(changes).filter((fieldName)=>this.activeFields[fieldName]&&this.activeFields[fieldName].onChange);if(!onChangeFields.length){return{};}
const localChanges=this._getChanges({...this._changes,...changes},{withReadonly:true});if(this.config.relationField){const parentRecord=this._parentRecord;localChanges[this.config.relationField]=parentRecord._getChanges(parentRecord._changes,{withReadonly:true});if(!this._parentRecord.isNew){localChanges[this.config.relationField].id=this._parentRecord.resId;}}
return this.model._onchange(this.config,{changes:localChanges,fieldNames:onChangeFields,evalContext:toRaw(this.evalContext),onError:(e)=>{const undoChanges=this._applyChanges(changes);undoChanges();throw e;},});}
async _update(changes,{withoutOnchange,withoutParentUpdate}={}){this.dirty=true;const prom=Promise.all([this._preprocessMany2oneChanges(changes),this._preprocessMany2OneReferenceChanges(changes),this._preprocessReferenceChanges(changes),this._preprocessX2manyChanges(changes),this._preprocessPropertiesChanges(changes),this._preprocessHtmlChanges(changes),]);if(!this.model._urgentSave){await prom;}
if(this.selected&&this.model.multiEdit){this._applyChanges(changes);return this.model.root._multiSave(this);}
let onchangeServerValues={};if(!this.model._urgentSave&&!withoutOnchange){onchangeServerValues=await this._getOnchangeValues(changes);}
for(const fieldName in changes){if(this.fields[fieldName].type==="many2one"){const curVal=toRaw(this.data[fieldName]);const nextVal=changes[fieldName];if(curVal&&nextVal&&curVal[0]===nextVal[0]&&curVal[1]===nextVal[1]){delete changes[fieldName];}}}
const undoChanges=this._applyChanges(changes,onchangeServerValues);if(Object.keys(changes).length>0||Object.keys(onchangeServerValues).length>0){try{await this._onUpdate({withoutParentUpdate});}catch(e){undoChanges();throw e;}
await this.model.hooks.onRecordChanged(this,this._getChanges());}}}
return __exports;});;

/* /web/static/src/model/relational_model/relational_model.js */
odoo.define('@web/model/relational_model/relational_model',['@odoo/owl','@web/core/context','@web/core/domain','@web/core/errors/error_dialogs','@web/core/utils/arrays','@web/core/utils/concurrency','@web/search/utils/order_by','@web/model/model','@web/model/relational_model/dynamic_group_list','@web/model/relational_model/dynamic_record_list','@web/model/relational_model/group','@web/model/relational_model/record','@web/model/relational_model/static_list','@web/model/relational_model/utils','@web/model/relational_model/errors'],function(require){'use strict';let __exports={};const{EventBus,markRaw,toRaw}=require("@odoo/owl");const{makeContext}=require("@web/core/context");const{Domain}=require("@web/core/domain");const{WarningDialog}=require("@web/core/errors/error_dialogs");const{shallowEqual}=require("@web/core/utils/arrays");const{KeepLast,Mutex}=require("@web/core/utils/concurrency");const{orderByToString}=require("@web/search/utils/order_by");const{Model}=require("@web/model/model");const{DynamicGroupList}=require("@web/model/relational_model/dynamic_group_list");const{DynamicRecordList}=require("@web/model/relational_model/dynamic_record_list");const{Group}=require("@web/model/relational_model/group");const{Record}=require("@web/model/relational_model/record");const{StaticList}=require("@web/model/relational_model/static_list");const{extractInfoFromGroupData,getBasicEvalContext,getFieldsSpec,isRelational,makeActiveField,}=require("@web/model/relational_model/utils");const{FetchRecordError}=require("@web/model/relational_model/errors");const DEFAULT_HOOKS={onWillLoadRoot:()=>{},onRootLoaded:()=>{},onWillSaveRecord:()=>{},onRecordSaved:()=>{},onWillSaveMulti:()=>{},onSavedMulti:()=>{},onWillSetInvalidField:()=>{},onRecordChanged:()=>{},};const RelationalModel=__exports.RelationalModel=class RelationalModel extends Model{static services=["action","company","dialog","notification","orm"];static Record=Record;static Group=Group;static DynamicRecordList=DynamicRecordList;static DynamicGroupList=DynamicGroupList;static StaticList=StaticList;static DEFAULT_LIMIT=80;static DEFAULT_COUNT_LIMIT=10000;static DEFAULT_GROUP_LIMIT=80;static DEFAULT_OPEN_GROUP_LIMIT=10;static MAX_NUMBER_OPENED_GROUPS=10;setup(params,{action,company,dialog,notification}){this.action=action;this.dialog=dialog;this.notification=notification;this.bus=new EventBus();this.keepLast=markRaw(new KeepLast());this.mutex=markRaw(new Mutex());this.config={isMonoRecord:false,currentCompanyId:company.currentCompany.id,context:{},...params.config,isRoot:true,};this.hooks=Object.assign({},DEFAULT_HOOKS,params.hooks);this.initialLimit=params.limit||this.constructor.DEFAULT_LIMIT;this.initialGroupsLimit=params.groupsLimit;this.initialCountLimit=params.countLimit||this.constructor.DEFAULT_COUNT_LIMIT;this.defaultOrderBy=params.defaultOrderBy;this.defaultGroupBy=params.defaultGroupBy;this.maxGroupByDepth=params.maxGroupByDepth;this.groupByInfo=params.groupByInfo||{};this.multiEdit=params.multiEdit;this.activeIdsLimit=params.activeIdsLimit||Number.MAX_SAFE_INTEGER;this.specialDataCaches=markRaw(params.state?.specialDataCaches||{});this.useSendBeaconToSaveUrgently=params.useSendBeaconToSaveUrgently||false;this._urgentSave=false;}
exportState(){return{config:toRaw(this.config),specialDataCaches:this.specialDataCaches,};}
hasData(){return this.root.hasData;}
async load(params={}){const config=this._getNextConfig(this.config,params);this.hooks.onWillLoadRoot(config);const data=await this.keepLast.add(this._loadData(config));this.root=this._createRoot(config,data);this.config=config;return this.hooks.onRootLoaded();}
async _getPropertyDefinition(config,propertyFullName){const result=await this.orm.call(config.resModel,"get_property_definition",[propertyFullName],{context:config.context});if(!result){config.groupBy=null;}else{result.propertyName=result.name;result.name=propertyFullName;result.relatedPropertyField={fieldName:propertyFullName.split(".")[0]};result.relation=result.comodel;config.fields[propertyFullName]=result;}}
_askChanges(){const proms=[];this.bus.trigger("NEED_LOCAL_CHANGES",{proms});return Promise.all([...proms,this.mutex.getUnlockedDef()]);}
_createRoot(config,data){if(config.isMonoRecord){return new this.constructor.Record(this,config,data);}
if(config.groupBy.length){return new this.constructor.DynamicGroupList(this,config,data);}
return new this.constructor.DynamicRecordList(this,config,data);}
_getNextConfig(currentConfig,params){const currentGroupBy=currentConfig.groupBy;const config=Object.assign({},currentConfig);config.context="context"in params?params.context:config.context;if(currentConfig.isMonoRecord){config.resId="resId"in params?params.resId:config.resId;config.resIds="resIds"in params?params.resIds:config.resIds;if(!config.resIds){config.resIds=config.resId?[config.resId]:[];}
if(!config.resId&&config.mode!=="edit"){config.mode="edit";}}else{config.domain="domain"in params?params.domain:config.domain;config.comparison="comparison"in params?params.comparison:config.comparison;config.groupBy="groupBy"in params?params.groupBy:config.groupBy;if(this.defaultGroupBy&&!config.groupBy.length){config.groupBy=[this.defaultGroupBy];}
if(this.maxGroupByDepth){config.groupBy=config.groupBy.slice(0,this.maxGroupByDepth);}
config.orderBy="orderBy"in params?params.orderBy:config.orderBy;if(!config.orderBy.length){config.orderBy=currentConfig.orderBy||[];}
if(this.defaultOrderBy&&!config.orderBy.length){config.orderBy=this.defaultOrderBy;}
if(!shallowEqual(config.groupBy||[],currentGroupBy||[])){delete config.groups;}
if(!config.groupBy.length){config.orderBy=config.orderBy.filter((order)=>order.name!=="__count");}}
if(!config.isMonoRecord&&params.domain){const resetOffset=(config)=>{config.offset=0;for(const group of Object.values(config.groups||{})){resetOffset(group.list);}};if(this.root){resetOffset(config);}
if(!!config.groupBy.length!==!!currentGroupBy?.length){delete config.limit;}}
return config;}
async _loadData(config){if(config.isMonoRecord){const evalContext=getBasicEvalContext(config);if(!config.resId){return this._loadNewRecord(config,{evalContext});}
const records=await this._loadRecords({...config,resIds:[config.resId],},evalContext);return records[0];}
if(config.resIds){const resIds=config.resIds.slice(config.offset,config.offset+config.limit);return this._loadRecords({...config,resIds});}
if(config.groupBy.length){return this._loadGroupedList(config);}
Object.assign(config,{limit:config.limit||this.initialLimit,countLimit:"countLimit"in config?config.countLimit:this.initialCountLimit,offset:config.offset||0,});if(config.countLimit!==Number.MAX_SAFE_INTEGER){config.countLimit=Math.max(config.countLimit,config.offset+config.limit);}
const{records,length}=await this._loadUngroupedList({...config,context:{...config.context,current_company_id:config.currentCompanyId,},});if(config.offset&&!records.length){config.offset=0;return this._loadData(config);}
return{records,length};}
async _loadGroupedList(config){config.offset=config.offset||0;config.limit=config.limit||this.initialGroupsLimit;if(!config.limit){config.limit=config.openGroupsByDefault?this.constructor.DEFAULT_OPEN_GROUP_LIMIT:this.constructor.DEFAULT_GROUP_LIMIT;}
config.groups=config.groups||{};const firstGroupByName=config.groupBy[0].split(":")[0];if(firstGroupByName.includes(".")){if(!config.fields[firstGroupByName]){await this._getPropertyDefinition(config,firstGroupByName);}
const propertiesFieldName=firstGroupByName.split(".")[0];if(!config.activeFields[propertiesFieldName]){config.activeFields[propertiesFieldName]=makeActiveField();}}
const orderBy=config.orderBy.filter((o)=>o.name===firstGroupByName||o.name==="__count"||(o.name in config.activeFields&&config.fields[o.name].aggregator!==undefined));const response=await this._webReadGroup(config,orderBy);const{groups:groupsData,length}=response;const groupBy=config.groupBy.slice(1);const groupByField=config.fields[config.groupBy[0].split(":")[0]];const commonConfig={resModel:config.resModel,fields:config.fields,activeFields:config.activeFields,};let groupRecordConfig;const groupRecordResIds=[];if(this.groupByInfo[firstGroupByName]){groupRecordConfig={...this.groupByInfo[firstGroupByName],resModel:config.fields[firstGroupByName].relation,context:{},};}
const proms=[];let nbOpenGroups=0;const groups=[];for(const groupData of groupsData){const group=extractInfoFromGroupData(groupData,config.groupBy,config.fields);if(!config.groups[group.value]){config.groups[group.value]={...commonConfig,groupByFieldName:groupByField.name,isFolded:"__fold"in groupData?groupData.__fold:!config.openGroupsByDefault,extraDomain:false,value:group.value,list:{...commonConfig,groupBy,},};if(isRelational(config.fields[firstGroupByName])&&!group.value){config.groups[group.value].isFolded=true;}
if(groupRecordConfig){config.groups[group.value].record={...groupRecordConfig,resId:group.value??false,};}}
if(groupRecordConfig){const resId=config.groups[group.value].record.resId;if(resId){groupRecordResIds.push(resId);}}
const groupConfig=config.groups[group.value];groupConfig.list.orderBy=config.orderBy;groupConfig.initialDomain=group.domain;if(groupConfig.extraDomain){groupConfig.list.domain=Domain.and([group.domain,groupConfig.extraDomain,]).toList();}else{groupConfig.list.domain=group.domain;}
const context={...config.context,[`default_${firstGroupByName}`]:group.serverValue,};groupConfig.list.context=context;groupConfig.context=context;if(groupBy.length){group.groups=[];}else{group.records=[];}
if(!groupConfig.isFolded){nbOpenGroups++;if(nbOpenGroups>this.constructor.MAX_NUMBER_OPENED_GROUPS){groupConfig.isFolded=true;}}
if(!groupConfig.isFolded&&group.count>0){const prom=this._loadData(groupConfig.list).then((response)=>{if(groupBy.length){group.groups=response?response.groups:[];group.length=response?response.length:0;}else{group.records=response?response.records:[];}});proms.push(prom);}
groups.push(group);}
if(groupRecordConfig&&Object.keys(groupRecordConfig.activeFields).length){const prom=this._loadRecords({...groupRecordConfig,resIds:groupRecordResIds,}).then((records)=>{for(const group of groups){if(!group.value){group.values={id:false};continue;}
group.values=records.find((r)=>group.value&&r.id===group.value);}});proms.push(prom);}
await Promise.all(proms);const params=JSON.stringify([config.domain,config.groupBy,config.offset,config.limit,config.orderBy,]);if(config.currentGroups&&config.currentGroups.params===params){const currentGroups=config.currentGroups.groups;currentGroups.forEach((group,index)=>{if(config.groups[group.value]&&!groups.some((g)=>JSON.stringify(g.value)===JSON.stringify(group.value))){const aggregates=Object.assign({},group.aggregates);for(const key in aggregates){aggregates[key]=0;}
groups.splice(index,0,Object.assign({},group,{count:0,length:0,records:[],aggregates}));}});}
config.currentGroups={params,groups};return{groups,length};}
async _loadNewRecord(config,params={}){return this._onchange(config,params);}
async _loadRecords(config,evalContext=config.context){const{resModel,resIds,activeFields,fields,context}=config;if(!resIds.length){return[];}
const fieldSpec=getFieldsSpec(activeFields,fields,evalContext);if(Object.keys(fieldSpec).length>0){const kwargs={context:{bin_size:true,...context},specification:fieldSpec,};const records=await this.orm.webRead(resModel,resIds,kwargs);if(!records.length){throw new FetchRecordError(resIds);}
return records;}else{return resIds.map((resId)=>{return{id:resId};});}}
async _loadUngroupedList(config){const orderBy=config.orderBy.filter((o)=>o.name!=="__count");const kwargs={specification:getFieldsSpec(config.activeFields,config.fields,config.context),offset:config.offset,order:orderByToString(orderBy),limit:config.limit,context:{bin_size:true,...config.context},count_limit:config.countLimit!==Number.MAX_SAFE_INTEGER?config.countLimit+1:undefined,};return this.orm.webSearchRead(config.resModel,config.domain,kwargs);}
async _onchange(config,{changes={},fieldNames=[],evalContext=config.context,onError}){const{fields,activeFields,resModel,resId}=config;let context=config.context;if(fieldNames.length===1){const fieldContext=config.activeFields[fieldNames[0]].context;context=makeContext([context,fieldContext],evalContext);}
const spec=getFieldsSpec(activeFields,fields,evalContext,{withInvisible:true});const args=[resId?[resId]:[],changes,fieldNames,spec];let response;try{response=await this.orm.call(resModel,"onchange",args,{context});}catch(e){if(onError){return onError(e);}
throw e;}
if(response.warning){const{type,title,message,className,sticky}=response.warning;if(type==="dialog"){this.dialog.add(WarningDialog,{title,message});}else{this.notification.add(message,{className,sticky,title,type:"warning",});}}
return response.value;}
async _updateConfig(config,patch,{reload=true,commit}={}){const tmpConfig={...config,...patch};markRaw(tmpConfig.activeFields);markRaw(tmpConfig.fields);let data;if(reload){if(tmpConfig.isRoot){this.hooks.onWillLoadRoot(tmpConfig);}
data=await this._loadData(tmpConfig);}
Object.assign(config,tmpConfig);if(data&&commit){commit(data);}
if(reload&&config.isRoot){return this.hooks.onRootLoaded();}}
async _updateCount(config){const count=await this.keepLast.add(this.orm.searchCount(config.resModel,config.domain,{context:config.context}));config.countLimit=Number.MAX_SAFE_INTEGER;return count;}
_updateSimilarRecords(reloadedRecord,serverValues){if(this.config.isMonoRecord||!this.config.groupBy.length){return;}
for(const record of this.root.records){if(record===reloadedRecord){continue;}
if(record.resId===reloadedRecord.resId){record._applyValues(serverValues);}}}
async _webReadGroup(config,orderBy){const aggregates=Object.values(config.fields).filter((field)=>field.aggregator&&field.name in config.activeFields&&field.name!==config.groupBy[0]).map((field)=>`${field.name}:${field.aggregator}`);return this.orm.webReadGroup(config.resModel,config.domain,aggregates,[config.groupBy[0]],{orderby:orderByToString(orderBy),lazy:true,offset:config.offset,limit:config.limit,context:config.context,});}}
return __exports;});;

/* /web/static/src/model/relational_model/static_list.js */
odoo.define('@web/model/relational_model/static_list',['@web/core/orm_service','@web/core/utils/arrays','@web/core/utils/objects','@web/model/relational_model/utils','@web/model/relational_model/datapoint','@odoo/owl'],function(require){'use strict';let __exports={};const{x2ManyCommands}=require("@web/core/orm_service");const{intersection}=require("@web/core/utils/arrays");const{pick}=require("@web/core/utils/objects");const{completeActiveFields}=require("@web/model/relational_model/utils");const{DataPoint}=require("@web/model/relational_model/datapoint");const{fromUnityToServerValues,getBasicEvalContext,getId,patchActiveFields}=require("@web/model/relational_model/utils");const{markRaw}=require("@odoo/owl");function compareFieldValues(v1,v2,fieldType){if(fieldType==="many2one"){v1=v1?v1[1]:"";v2=v2?v2[1]:"";}
return v1<v2;}
function compareRecords(r1,r2,orderBy,fields){const{name,asc}=orderBy[0];function getValue(record,fieldName){return fieldName==="id"?record.resId:record.data[fieldName];}
const v1=asc?getValue(r1,name):getValue(r2,name);const v2=asc?getValue(r2,name):getValue(r1,name);if(compareFieldValues(v1,v2,fields[name].type)){return-1;}
if(compareFieldValues(v2,v1,fields[name].type)){return 1;}
if(orderBy.length>1){return compareRecords(r1,r2,orderBy.slice(1),fields);}
return 0;}
const StaticList=__exports.StaticList=class StaticList extends DataPoint{static type="StaticList";setup(config,data,options={}){this._parent=options.parent;this._onUpdate=options.onUpdate;this._cache=markRaw({});this._commands=[];this._initialCommands=[];this._savePoint=undefined;this._unknownRecordCommands={};this._currentIds=[...this.resIds];this._initialCurrentIds=[...this.currentIds];this._needsReordering=false;this._tmpIncreaseLimit=0;this._extendedRecords=new Set();this.records=data.slice(this.offset,this.limit).map((r)=>this._createRecordDatapoint(r));this.count=this.resIds.length;this.handleField=Object.keys(this.activeFields).find((fieldName)=>this.activeFields[fieldName].isHandle);}
get currentIds(){return this._currentIds;}
get editedRecord(){return this.records.find((record)=>record.isInEdition);}
get evalContext(){const evalContext=getBasicEvalContext(this.config);evalContext.parent=this._parent.evalContext;return evalContext;}
get limit(){return this.config.limit;}
get offset(){return this.config.offset;}
get orderBy(){return this.config.orderBy;}
get resIds(){return this.config.resIds;}
addNewRecord(params){return this.model.mutex.exec(async()=>{const{activeFields,context,mode,position,withoutParent}=params;const record=await this._createNewRecordDatapoint({activeFields,context,position,withoutParent,manuallyAdded:true,mode,});await this._addRecord(record,{position});await this._onUpdate({withoutOnchange:!record._checkValidity({silent:true})});return record;});}
canResequence(){return this.handleField&&this.orderBy.length&&this.orderBy[0].name===this.handleField;}
delete(record){return this.model.mutex.exec(async()=>{await this._applyCommands([[x2ManyCommands.DELETE,record.resId||record._virtualId]]);await this._onUpdate();});}
async enterEditMode(record){const canProceed=await this.leaveEditMode();if(canProceed){await record.switchMode("edit");}
return canProceed;}
extendRecord(params,record){return this.model.mutex.exec(async()=>{completeActiveFields(this.config.activeFields,params.activeFields);Object.assign(this.fields,params.fields);const activeFields={...params.activeFields};for(const fieldName in this.activeFields){if(fieldName in activeFields){patchActiveFields(activeFields[fieldName],this.activeFields[fieldName]);}else{activeFields[fieldName]=this.activeFields[fieldName];}}
if(record){record._noUpdateParent=true;record._activeFieldsToRestore={...this.config.activeFields};const config={...record.config,...params,activeFields,};if(this._extendedRecords.has(record.id)){this.model._updateConfig(record.config,config,{reload:false});record._addSavePoint();return record;}
let data={};if(!record.isNew){const evalContext=Object.assign({},record.evalContext,config.context);const resIds=[record.resId];[data]=await this.model._loadRecords({...config,resIds},evalContext);}
this.model._updateConfig(record.config,config,{reload:false});record._applyDefaultValues();for(const fieldName in record.activeFields){if(["one2many","many2many"].includes(record.fields[fieldName].type)){const list=record.data[fieldName];const patch={activeFields:activeFields[fieldName].related.activeFields,fields:activeFields[fieldName].related.fields,};for(const subRecord of Object.values(list._cache)){this.model._updateConfig(subRecord.config,patch,{reload:false,});}
this.model._updateConfig(list.config,patch,{reload:false});}}
record._applyValues(data);const commands=this._unknownRecordCommands[record.resId];delete this._unknownRecordCommands[record.resId];if(commands){this._applyCommands(commands);}
record._addSavePoint();}else{record=await this._createNewRecordDatapoint({activeFields,context:params.context,withoutParent:params.withoutParent,manuallyAdded:true,});record._activeFieldsToRestore={...this.config.activeFields};record._noUpdateParent=true;}
this._extendedRecords.add(record.id);return record;});}
forget(record){return this.model.mutex.exec(async()=>{await this._applyCommands([[x2ManyCommands.UNLINK,record.resId]]);await this._onUpdate();});}
async leaveEditMode({discard,canAbandon,validate}={}){if(this.editedRecord){await this.model._askChanges(false);}
return this.model.mutex.exec(async()=>{let editedRecord=this.editedRecord;if(editedRecord){const isValid=editedRecord._checkValidity();if(!isValid&&validate){return false;}
if(canAbandon!==false&&!validate){this._abandonRecords([editedRecord],{force:true});}
editedRecord=this.editedRecord;if(editedRecord){if(isValid&&!editedRecord.dirty&&discard){return false;}
if(isValid||(!editedRecord.dirty&&!editedRecord._manuallyAdded)){editedRecord._switchMode("readonly");}}}
return!this.editedRecord;});}
linkTo(resId,serverData){return this.model.mutex.exec(async()=>{await this._applyCommands([[x2ManyCommands.LINK,resId,serverData]]);await this._onUpdate();});}
unlinkFrom(resId,serverData){return this.model.mutex.exec(async()=>{await this._applyCommands([[x2ManyCommands.UNLINK,resId,serverData]]);await this._onUpdate();});}
load({limit,offset,orderBy}={}){return this.model.mutex.exec(async()=>{const editedRecord=this.editedRecord;if(editedRecord&&!(await editedRecord.checkValidity())){return;}
limit=limit!==undefined?limit:this.limit;offset=offset!==undefined?offset:this.offset;orderBy=orderBy!==undefined?orderBy:this.orderBy;return this._load({limit,offset,orderBy});});}
moveRecord(dataRecordId,_dataGroupId,refId,_targetGroupId){return this.resequence(dataRecordId,refId);}
sortBy(fieldName){return this.model.mutex.exec(()=>this._sortBy(fieldName));}
async addAndRemove({add,remove,reload}={}){return this.model.mutex.exec(async()=>{const commands=[...(add||[]).map((id)=>[x2ManyCommands.LINK,id]),...(remove||[]).map((id)=>[x2ManyCommands.UNLINK,id]),];await this._applyCommands(commands,{canAddOverLimit:true,reload});await this._onUpdate();});}
async resequence(movedId,targetId){return this.model.mutex.exec(()=>this._resequence(movedId,targetId));}
validateExtendedRecord(record){return this.model.mutex.exec(async()=>{if(!this._currentIds.includes(record.isNew?record._virtualId:record.resId)){await this._addRecord(record);}else if(!record.dirty){return;}
await this._onUpdate();record._restoreActiveFields();record._savePoint=undefined;});}
_abandonRecords(records=this.records,{force}={}){for(const record of records){if(record.canBeAbandoned&&(force||!record._checkValidity())){const virtualId=record._virtualId;const index=this._currentIds.findIndex((id)=>id===virtualId);this._currentIds.splice(index,1);this.records.splice(this.records.findIndex((r)=>r===record),1);this._commands=this._commands.filter((c)=>c[1]!==virtualId);this.count--;if(this._tmpIncreaseLimit>0){this.model._updateConfig(this.config,{limit:this.limit-1},{reload:false});this._tmpIncreaseLimit--;}}}}
async _addRecord(record,{position}={}){const command=[x2ManyCommands.CREATE,record._virtualId];if(position==="top"){this.records.unshift(record);if(this.records.length>this.limit){this.records.pop();}
this._currentIds.splice(this.offset,0,record._virtualId);this._commands.unshift(command);}else if(position==="bottom"){this.records.push(record);this._currentIds.splice(this.offset+this.limit,0,record._virtualId);if(this.records.length>this.limit){this._tmpIncreaseLimit++;const nextLimit=this.limit+1;this.model._updateConfig(this.config,{limit:nextLimit},{reload:false});}
this._commands.push(command);}else{const currentIds=[...this._currentIds,record._virtualId];if(this.orderBy.length){await this._sort(currentIds);}else{if(this.records.length<this.limit){this.records.push(record);}}
this._currentIds=currentIds;this._commands.push(command);}
this.count++;this._needsReordering=true;}
_addSavePoint(){for(const id in this._cache){this._cache[id]._addSavePoint();}
this._savePoint=markRaw({_commands:[...this._commands],_currentIds:[...this._currentIds],count:this.count,});}
_applyCommands(commands,{canAddOverLimit,reload}={}){const{CREATE,UPDATE,DELETE,UNLINK,LINK,SET}=x2ManyCommands;let lastCommandIndex=-1;const commandsByIds={};function addOwnCommand(command){commandsByIds[command[1]]=commandsByIds[command[1]]||[];commandsByIds[command[1]].push({command,index:++lastCommandIndex});}
function getOwnCommands(id){commandsByIds[id]=commandsByIds[id]||[];return commandsByIds[id];}
for(const command of this._commands){addOwnCommand(command);}
const removedIds={};const recordsToLoad=[];for(const command of commands){switch(command[0]){case CREATE:{const virtualId=getId("virtual");const record=this._createRecordDatapoint(command[2],{virtualId});this.records.push(record);addOwnCommand([CREATE,virtualId]);const index=this.offset+this.limit;this._currentIds.splice(index,0,virtualId);this._tmpIncreaseLimit=Math.max(this.records.length-this.limit,0);const nextLimit=this.limit+this._tmpIncreaseLimit;this.model._updateConfig(this.config,{limit:nextLimit},{reload:false});this.count++;break;}
case UPDATE:{const existingCommand=getOwnCommands(command[1]).some((x)=>x.command[0]===CREATE||x.command[0]===UPDATE);if(!existingCommand){addOwnCommand([UPDATE,command[1]]);}
const record=this._cache[command[1]];if(!record){if(!(command[1]in this._unknownRecordCommands)){this._unknownRecordCommands[command[1]]=[];}
this._unknownRecordCommands[command[1]].push(command);}else if(command[1]in this._unknownRecordCommands){this._unknownRecordCommands[command[1]].push(command);}else{const changes={};for(const fieldName in command[2]){if(["one2many","many2many"].includes(this.fields[fieldName].type)){const invisible=record.activeFields[fieldName]?.invisible;if(invisible==="True"||invisible==="1"||!(fieldName in record.activeFields)){if(!(command[1]in this._unknownRecordCommands)){this._unknownRecordCommands[command[1]]=[];}
this._unknownRecordCommands[command[1]].push(command);continue;}}
changes[fieldName]=command[2][fieldName];}
record._applyChanges(record._parseServerValues(changes,record.data));}
break;}
case DELETE:case UNLINK:{if(command[0]===UNLINK){const firstCommand=this._commands[0];const hasReplaceWithCommand=firstCommand&&firstCommand[0]===SET;if(hasReplaceWithCommand&&firstCommand[2].includes(command[1])){firstCommand[2]=firstCommand[2].filter((id)=>id!==command[1]);break;}}
const ownCommands=getOwnCommands(command[1]);if(command[0]===DELETE){const hasCreateCommand=ownCommands.some((x)=>x.command[0]===CREATE);ownCommands.splice(0);if(!hasCreateCommand){addOwnCommand([DELETE,command[1]]);}}else{const linkToIndex=ownCommands.findIndex((x)=>x.command[0]===LINK);if(linkToIndex>=0){ownCommands.splice(linkToIndex,1);}else{addOwnCommand([UNLINK,command[1]]);}}
removedIds[command[1]]=true;break;}
case LINK:{let record;if(command[1]in this._cache){record=this._cache[command[1]];}else{record=this._createRecordDatapoint({...command[2],id:command[1]});}
if(!this.limit||this.records.length<this.limit||canAddOverLimit){if(!command[2]){recordsToLoad.push(record);}
this.records.push(record);if(this.records.length>this.limit){this._tmpIncreaseLimit=this.records.length-this.limit;const nextLimit=this.limit+this._tmpIncreaseLimit;this.model._updateConfig(this.config,{limit:nextLimit},{reload:false});}}
this._currentIds.push(record.resId);addOwnCommand([command[0],command[1]]);this.count++;break;}}}
this._commands=Object.values(commandsByIds).flat().sort((x,y)=>x.index-y.index).map((x)=>x.command);if(Object.keys(removedIds).length){let removeCommandsByIdsCopy=Object.assign({},removedIds);this.records=this.records.filter((r)=>{const id=r.resId||r._virtualId;if(removeCommandsByIdsCopy[id]){delete removeCommandsByIdsCopy[id];return false;}
return true;});const nextCurrentIds=[];removeCommandsByIdsCopy=Object.assign({},removedIds);for(const id of this._currentIds){if(removeCommandsByIdsCopy[id]){delete removeCommandsByIdsCopy[id];}else{nextCurrentIds.push(id);}}
this._currentIds=nextCurrentIds;this.count=this._currentIds.length;}
const nbMissingRecords=this.limit-this.records.length;if(nbMissingRecords>0){const lastRecordIndex=this.limit+this.offset;const firstRecordIndex=lastRecordIndex-nbMissingRecords;const nextRecordIds=this._currentIds.slice(firstRecordIndex,lastRecordIndex);for(const id of this._getResIdsToLoad(nextRecordIds)){const record=this._createRecordDatapoint({id},{dontApplyCommands:true});recordsToLoad.push(record);}
for(const id of nextRecordIds){this.records.push(this._cache[id]);}}
if(recordsToLoad.length||reload){const resIds=reload?this.records.map((r)=>r.resId):recordsToLoad.map((r)=>r.resId);return this.model._loadRecords({...this.config,resIds}).then((recordValues)=>{if(reload){for(const record of recordValues){this._createRecordDatapoint(record);}
this.records=resIds.map((id)=>this._cache[id]);return;}
for(let i=0;i<recordsToLoad.length;i++){const record=recordsToLoad[i];record._applyValues(recordValues[i]);const commands=this._unknownRecordCommands[record.resId];if(commands){delete this._unknownRecordCommands[record.resId];this._applyCommands(commands);}}});}}
_applyInitialCommands(commands){this._applyCommands(commands);this._initialCommands=[...commands];this._initialCurrentIds=[...this._currentIds];}
async _createNewRecordDatapoint(params={}){const changes={};if(!params.withoutParent&&this.config.relationField){changes[this.config.relationField]=this._parent._getChanges();if(!this._parent.isNew){changes[this.config.relationField].id=this._parent.resId;}}
const values=await this.model._loadNewRecord({resModel:this.resModel,activeFields:params.activeFields||this.activeFields,fields:this.fields,context:Object.assign({},this.context,params.context),},{changes,evalContext:this.evalContext});if(this.canResequence()&&this.records.length){const position=params.position||"bottom";const order=this.orderBy[0];const asc=!order||order.asc;let value;if(position==="top"){const isOnFirstPage=this.offset===0;value=this.records[0].data[this.handleField];if(isOnFirstPage){if(asc){value=value>0?value-1:0;}else{value=value+1;}}}else if(position==="bottom"){value=this.records[this.records.length-1].data[this.handleField];const isOnLastPage=this.limit+this.offset>=this.count;if(isOnLastPage){if(asc){value=value+1;}else{value=value>0?value-1:0;}}}
values[this.handleField]=value;}
return this._createRecordDatapoint(values,{mode:params.mode||"edit",virtualId:getId("virtual"),activeFields:params.activeFields,manuallyAdded:params.manuallyAdded,});}
_createRecordDatapoint(data,params={}){const resId=data.id||false;if(!resId&&!params.virtualId){throw new Error("You must provide a virtualId if the record has no id");}
const id=resId||params.virtualId;const config={context:this.context,activeFields:Object.assign({},params.activeFields||this.activeFields),resModel:this.resModel,fields:params.fields||this.fields,relationField:this.config.relationField,resId,resIds:resId?[resId]:[],mode:params.mode||"readonly",isMonoRecord:true,currentCompanyId:this.currentCompanyId,};const{CREATE,UPDATE}=x2ManyCommands;const options={parentRecord:this._parent,onUpdate:async({withoutParentUpdate})=>{const id=record.isNew?record._virtualId:record.resId;if(!this.currentIds.includes(id)){return;}
const hasCommand=this._commands.some((c)=>(c[0]===CREATE||c[0]===UPDATE)&&c[1]===id);if(!hasCommand){this._commands.push([UPDATE,id]);}
if(record._noUpdateParent){return;}
if(!withoutParentUpdate){await this._onUpdate({withoutOnchange:!record._checkValidity({silent:true}),});}},virtualId:params.virtualId,manuallyAdded:params.manuallyAdded,};const record=new this.model.constructor.Record(this.model,config,data,options);this._cache[id]=record;if(!params.dontApplyCommands){const commands=this._unknownRecordCommands[id];if(commands){delete this._unknownRecordCommands[id];this._applyCommands(commands);}}
return record;}
_clearCommands(){this._commands=[];this._unknownRecordCommands={};}
_discard(){for(const id in this._cache){this._cache[id]._discard();}
if(this._savePoint){this._commands=this._savePoint._commands;this._currentIds=this._savePoint._currentIds;this.count=this._savePoint.count;}else{this._commands=[];this._currentIds=[...this.resIds];this.count=this.resIds.length;}
this._unknownRecordCommands={};const limit=this.limit-this._tmpIncreaseLimit;this._tmpIncreaseLimit=0;this.model._updateConfig(this.config,{limit},{reload:false});this.records=this._currentIds.slice(this.offset,this.limit).map((resId)=>this._cache[resId]);if(!this._savePoint){this._applyCommands(this._initialCommands);}
this._savePoint=undefined;}
_getCommands({withReadonly}={}){const{CREATE,UPDATE,LINK}=x2ManyCommands;const commands=[];for(const command of this._commands){if(command[0]===UPDATE&&command[1]in this._unknownRecordCommands){const uCommands=this._unknownRecordCommands[command[1]];for(const uCommand of uCommands){const values=fromUnityToServerValues(uCommand[2],this.fields,this.activeFields,{withReadonly,context:this.context});commands.push([uCommand[0],uCommand[1],values]);}}else if(command[0]===CREATE||command[0]===UPDATE){const record=this._cache[command[1]];if(command[0]===CREATE&&record.resId){commands.push([LINK,record.resId]);}else{const values=record._getChanges(record._changes,{withReadonly});if(command[0]===CREATE||Object.keys(values).length){commands.push([command[0],command[1],values]);}}}else{commands.push(command);}}
return commands;}
_getResIdsToLoad(resIds,fieldNames=this.fieldNames){return resIds.filter((resId)=>{if(typeof resId==="string"){return false;}
const record=this._cache[resId];if(!record){return true;}
fieldNames=fieldNames.filter((fieldName)=>fieldName!=="id");return intersection(fieldNames,record.fieldNames).length!==fieldNames.length;});}
async _load({limit=this.limit,offset=this.offset,orderBy=this.orderBy,nextCurrentIds=this._currentIds,}={}){const currentIds=nextCurrentIds.slice(offset,offset+limit);const resIds=this._getResIdsToLoad(currentIds);if(resIds.length){const records=await this.model._loadRecords({...this.config,resIds},this.evalContext);for(const record of records){this._createRecordDatapoint(record);}}
this.records=currentIds.map((id)=>this._cache[id]);this._currentIds=nextCurrentIds;await this.model._updateConfig(this.config,{limit,offset,orderBy},{reload:false});}
async _replaceWith(ids,{reload=false}={}){const resIds=reload?ids:ids.filter((id)=>!this._cache[id]);if(resIds.length){const records=await this.model._loadRecords({...this.config,resIds,context:this.context,});for(const record of records){this._createRecordDatapoint(record);}}
this.records=ids.map((id)=>this._cache[id]);const updateCommandsToKeep=this._commands.filter((c)=>c[0]===x2ManyCommands.UPDATE&&ids.includes(c[1]));this._commands=[x2ManyCommands.set(ids)].concat(updateCommandsToKeep);this._currentIds=[...ids];this.count=this._currentIds.length;if(this._currentIds.length>this.limit){this._tmpIncreaseLimit=this._currentIds.length-this.limit;const nextLimit=this.limit+this._tmpIncreaseLimit;this.model._updateConfig(this.config,{limit:nextLimit},{reload:false});}}
async _resequence(movedId,targetId){const records=[...this.records];const order=this.orderBy.find((o)=>o.name===this.handleField);const asc=!order||order.asc;const fromIndex=records.findIndex((r)=>r.id===movedId);let toIndex=0;if(targetId!==null){const targetIndex=records.findIndex((r)=>r.id===targetId);toIndex=fromIndex>targetIndex?targetIndex+1:targetIndex;}
const getSequence=(rec)=>rec&&rec.data[this.handleField];const firstIndex=Math.min(fromIndex,toIndex);const lastIndex=Math.max(fromIndex,toIndex)+1;let reorderAll=false;let lastSequence=(asc?-1:1)*Infinity;for(let index=0;index<records.length;index++){const sequence=getSequence(records[index]);if((asc&&lastSequence>=sequence)||(!asc&&lastSequence<=sequence)){reorderAll=true;break;}
lastSequence=sequence;}
const[record]=records.splice(fromIndex,1);records.splice(toIndex,0,record);let toReorder=records;if(!reorderAll){toReorder=toReorder.slice(firstIndex,lastIndex).filter((r)=>r.id!==movedId);if(fromIndex<toIndex){toReorder.push(record);}else{toReorder.unshift(record);}}
if(!asc){toReorder.reverse();}
const sequences=toReorder.map(getSequence);const offset=sequences.length&&Math.min(...sequences);const proms=[];for(const[i,record]of Object.entries(toReorder)){proms.push(record._update({[this.handleField]:offset+Number(i)},{withoutParentUpdate:true}));}
await Promise.all(proms);await this._sort();await this._onUpdate();}
async _sort(currentIds=this.currentIds,orderBy=this.orderBy){const fieldNames=orderBy.map((o)=>o.name);const resIds=this._getResIdsToLoad(currentIds,fieldNames);if(resIds.length){const activeFields=pick(this.activeFields,...fieldNames);const config={...this.config,resIds,activeFields};const records=await this.model._loadRecords(config);for(const record of records){this._createRecordDatapoint(record,{activeFields});}}
const allRecords=currentIds.map((id)=>this._cache[id]);const sortedRecords=allRecords.sort((r1,r2)=>{return compareRecords(r1,r2,orderBy,this.fields);});await this._load({orderBy,nextCurrentIds:sortedRecords.map((r)=>r.resId||r._virtualId),});this._needsReordering=false;}
async _sortBy(fieldName){let orderBy=[...this.orderBy];if(fieldName){if(orderBy.length&&orderBy[0].name===fieldName){if(!this._needsReordering){orderBy[0]={name:orderBy[0].name,asc:!orderBy[0].asc};}}else{orderBy=orderBy.filter((o)=>o.name!==fieldName);orderBy.unshift({name:fieldName,asc:true,});}}
return this._sort(this._currentIds,orderBy);}
_updateContext(context){Object.assign(this.context,context);for(const record of Object.values(this._cache)){record._setEvalContext();}}}
return __exports;});;

/* /web/static/src/model/relational_model/utils.js */
odoo.define('@web/model/relational_model/utils',['@odoo/owl','@web/core/context','@web/core/domain','@web/core/l10n/dates','@web/core/orm_service','@web/core/py_js/py','@web/core/utils/concurrency','@web/core/utils/objects','@web/core/utils/reactive','@web/core/utils/timing','@web/search/utils/order_by','@web/core/network/rpc'],function(require){'use strict';let __exports={};const{markup,onWillDestroy,onWillStart,onWillUpdateProps,useComponent}=require("@odoo/owl");const{evalPartialContext,makeContext}=require("@web/core/context");const{Domain}=require("@web/core/domain");const{deserializeDate,deserializeDateTime,serializeDate,serializeDateTime,}=require("@web/core/l10n/dates");const{x2ManyCommands}=require("@web/core/orm_service");const{evaluateExpr}=require("@web/core/py_js/py");const{Deferred}=require("@web/core/utils/concurrency");const{omit}=require("@web/core/utils/objects");const{effect}=require("@web/core/utils/reactive");const{batched}=require("@web/core/utils/timing");const{orderByToString}=require("@web/search/utils/order_by");const{rpc}=require("@web/core/network/rpc");function convertBoolToPyExpr(value){if(value===true||value===false){return value?"True":"False";}
return value;}
__exports.makeActiveField=makeActiveField;function makeActiveField({context,invisible,readonly,required,onChange,forceSave,isHandle,}={}){return{context:context||"{}",invisible:convertBoolToPyExpr(invisible||false),readonly:convertBoolToPyExpr(readonly||false),required:convertBoolToPyExpr(required||false),onChange:onChange||false,forceSave:forceSave||false,isHandle:isHandle||false,};}
const AGGREGATABLE_FIELD_TYPES=["float","integer","monetary"];__exports.addFieldDependencies=addFieldDependencies;function addFieldDependencies(activeFields,fields,fieldDependencies=[]){for(const field of fieldDependencies){if(!("readonly"in field)){field.readonly=true;}
if(field.name in activeFields){patchActiveFields(activeFields[field.name],makeActiveField(field));}else{activeFields[field.name]=makeActiveField(field);if(["one2many","many2many"].includes(field.type)){activeFields[field.name].related={activeFields:{},fields:{}};}}
if(!fields[field.name]){const newField=omit(field,["context","invisible","required","readonly","onChange",]);fields[field.name]=newField;if(newField.type==="selection"&&!Array.isArray(newField.selection)){newField.selection=[];}}}}
function completeActiveField(activeField,extra){if(extra.related){for(const fieldName in extra.related.activeFields){if(fieldName in activeField.related.activeFields){completeActiveField(activeField.related.activeFields[fieldName],extra.related.activeFields[fieldName]);}else{activeField.related.activeFields[fieldName]={...extra.related.activeFields[fieldName],};}}
Object.assign(activeField.related.fields,extra.related.fields);}}
__exports.completeActiveFields=completeActiveFields;function completeActiveFields(activeFields,extraActiveFields){for(const fieldName in extraActiveFields){const extraActiveField={...extraActiveFields[fieldName],invisible:"True",};if(fieldName in activeFields){completeActiveField(activeFields[fieldName],extraActiveField);}else{activeFields[fieldName]=extraActiveField;}}}
__exports.createPropertyActiveField=createPropertyActiveField;function createPropertyActiveField(property){const{type}=property;const activeField=makeActiveField();if(type==="one2many"||type==="many2many"){activeField.related={fields:{id:{name:"id",type:"integer"},display_name:{name:"display_name",type:"char"},},activeFields:{id:makeActiveField({readonly:true}),display_name:makeActiveField(),},};}
return activeField;}
__exports.combineModifiers=combineModifiers;function combineModifiers(mod1,mod2,operator){if(operator==="AND"){if(!mod1||mod1==="False"||!mod2||mod2==="False"){return"False";}
if(mod1==="True"){return mod2;}
if(mod2==="True"){return mod1;}
return"("+mod1+") and ("+mod2+")";}else if(operator==="OR"){if(mod1==="True"||mod2==="True"){return"True";}
if(!mod1||mod1==="False"){return mod2;}
if(!mod2||mod2==="False"){return mod1;}
return"("+mod1+") or ("+mod2+")";}
throw new Error(`Operator provided to "combineModifiers" must be "AND" or "OR", received ${operator}`);}
__exports.patchActiveFields=patchActiveFields;function patchActiveFields(activeField,patch){activeField.invisible=combineModifiers(activeField.invisible,patch.invisible,"AND");activeField.readonly=combineModifiers(activeField.readonly,patch.readonly,"AND");activeField.required=combineModifiers(activeField.required,patch.required,"OR");activeField.onChange=activeField.onChange||patch.onChange;activeField.forceSave=activeField.forceSave||patch.forceSave;activeField.isHandle=activeField.isHandle||patch.isHandle;if(patch.related){const related=activeField.related;for(const fieldName in patch.related.activeFields){if(fieldName in related.activeFields){patchActiveFields(related.activeFields[fieldName],patch.related.activeFields[fieldName]);}else{related.activeFields[fieldName]={...patch.related.activeFields[fieldName]};}}
Object.assign(related.fields,patch.related.fields);}
if("limit"in patch){activeField.limit=patch.limit;}
if(patch.defaultOrderBy){activeField.defaultOrderBy=patch.defaultOrderBy;}}
__exports.extractFieldsFromArchInfo=extractFieldsFromArchInfo;function extractFieldsFromArchInfo({fieldNodes,widgetNodes},fields){const activeFields={};for(const fieldNode of Object.values(fieldNodes)){const fieldName=fieldNode.name;const activeField=makeActiveField({context:fieldNode.context,invisible:combineModifiers(fieldNode.invisible,fieldNode.column_invisible,"OR"),readonly:fieldNode.readonly,required:fieldNode.required,onChange:fieldNode.onChange,forceSave:fieldNode.forceSave,isHandle:fieldNode.isHandle,});if(["one2many","many2many"].includes(fields[fieldName].type)){activeField.related={activeFields:{},fields:{},};if(fieldNode.views){const viewDescr=fieldNode.views[fieldNode.viewMode];if(viewDescr){activeField.related=extractFieldsFromArchInfo(viewDescr,viewDescr.fields);activeField.limit=viewDescr.limit;activeField.defaultOrderBy=viewDescr.defaultOrder;if(fieldNode.views.form){const formArchInfo=extractFieldsFromArchInfo(fieldNode.views.form,fieldNode.views.form.fields);completeActiveFields(activeField.related.activeFields,formArchInfo.activeFields);Object.assign(activeField.related.fields,formArchInfo.fields);}
if(fieldNode.viewMode!=="default"&&fieldNode.views.default){const defaultArchInfo=extractFieldsFromArchInfo(fieldNode.views.default,fieldNode.views.default.fields);for(const fieldName in defaultArchInfo.activeFields){if(fieldName in activeField.related.activeFields){patchActiveFields(activeField.related.activeFields[fieldName],defaultArchInfo.activeFields[fieldName]);}else{activeField.related.activeFields[fieldName]={...defaultArchInfo.activeFields[fieldName],};}}
activeField.related.fields=Object.assign({},defaultArchInfo.fields,activeField.related.fields);}}}
if(fieldNode.field?.useSubView){activeField.required="False";}}
if(fields[fieldName].type==="many2one_reference"&&fieldNode.views){const viewDescr=fieldNode.views.default;activeField.related=extractFieldsFromArchInfo(viewDescr,viewDescr.fields);}
if(fieldName in activeFields){patchActiveFields(activeFields[fieldName],activeField);}else{activeFields[fieldName]=activeField;}
if(fieldNode.field){let fieldDependencies=fieldNode.field.fieldDependencies;if(typeof fieldDependencies==="function"){fieldDependencies=fieldDependencies(fieldNode);}
addFieldDependencies(activeFields,fields,fieldDependencies);}}
for(const widgetInfo of Object.values(widgetNodes||{})){let fieldDependencies=widgetInfo.widget.fieldDependencies;if(typeof fieldDependencies==="function"){fieldDependencies=fieldDependencies(widgetInfo);}
addFieldDependencies(activeFields,fields,fieldDependencies);}
return{activeFields,fields};}
__exports.getFieldContext=getFieldContext;function getFieldContext(record,fieldName,rawContext=record.activeFields[fieldName].context){const context={};for(const key in record.context){if(!key.startsWith("default_")&&!key.startsWith("search_default_")&&!key.endsWith("_view_ref")){context[key]=record.context[key];}}
return{...context,...record.fields[fieldName].context,...makeContext([rawContext],record.evalContext),};}
__exports.getFieldDomain=getFieldDomain;function getFieldDomain(record,fieldName,domain){if(typeof domain==="function"){domain=domain();domain=typeof domain==="function"?domain():domain;}
if(domain){return domain;}
domain=record.fields[fieldName].domain;return typeof domain==="string"?new Domain(evaluateExpr(domain,record.evalContext)).toList():domain||[];}
__exports.getBasicEvalContext=getBasicEvalContext;function getBasicEvalContext(config){const{uid,allowed_company_ids}=config.context;return{context:config.context,uid,allowed_company_ids,current_company_id:config.currentCompanyId,};}
function getFieldContextForSpec(activeFields,fields,fieldName,evalContext){let context=activeFields[fieldName].context;if(!context||context==="{}"){context=fields[fieldName].context||{};}else{context=evalPartialContext(context,evalContext);}
if(Object.keys(context).length>0){return context;}}
__exports.getFieldsSpec=getFieldsSpec;function getFieldsSpec(activeFields,fields,evalContext,{withInvisible}={}){const fieldsSpec={};const properties=[];for(const fieldName in activeFields){if(fields[fieldName].relatedPropertyField){continue;}
const{related,limit,defaultOrderBy,invisible}=activeFields[fieldName];const isAlwaysInvisible=invisible==="True"||invisible==="1";fieldsSpec[fieldName]={};switch(fields[fieldName].type){case"one2many":case"many2many":{if(related&&(withInvisible||!isAlwaysInvisible)){fieldsSpec[fieldName].fields=getFieldsSpec(related.activeFields,related.fields,evalContext,{withInvisible});fieldsSpec[fieldName].context=getFieldContextForSpec(activeFields,fields,fieldName,evalContext);fieldsSpec[fieldName].limit=limit;if(defaultOrderBy){fieldsSpec[fieldName].order=orderByToString(defaultOrderBy);}}
break;}
case"many2one":case"reference":{fieldsSpec[fieldName].fields={};if(!isAlwaysInvisible){fieldsSpec[fieldName].fields.display_name={};fieldsSpec[fieldName].context=getFieldContextForSpec(activeFields,fields,fieldName,evalContext);}
break;}
case"many2one_reference":{if(related&&!isAlwaysInvisible){fieldsSpec[fieldName].fields=getFieldsSpec(related.activeFields,related.fields,evalContext);fieldsSpec[fieldName].context=getFieldContextForSpec(activeFields,fields,fieldName,evalContext);}
break;}
case"properties":{properties.push(fieldName);break;}}}
for(const fieldName of properties){const fieldSpec=fieldsSpec[fields[fieldName].definition_record];if(fieldSpec){if(!fieldSpec.fields){fieldSpec.fields={};}
fieldSpec.fields.display_name={};}}
return fieldsSpec;}
let nextId=0;__exports.getId=getId;function getId(prefix=""){return`${prefix}_${++nextId}`;}
__exports.parseServerValue=parseServerValue;function parseServerValue(field,value){switch(field.type){case"char":case"text":{return value||"";}
case"html":{return markup(value||"");}
case"date":{return value?deserializeDate(value):false;}
case"datetime":{return value?deserializeDateTime(value):false;}
case"selection":{if(value===false){const hasKey0=field.selection.find((option)=>option[0]===0);return hasKey0?0:value;}
return value;}
case"reference":{if(value===false){return false;}
return{resId:value.id.id,resModel:value.id.model,displayName:value.display_name,};}
case"many2one_reference":{if(value===0){return false;}
if(typeof value==="number"){return{resId:value};}
return{resId:value.id,displayName:value.display_name,};}
case"many2one":{if(Array.isArray(value)){return value;}
return value?[value.id,value.display_name]:false;}
case"properties":{return value?value.map((property)=>({...property,value:parseServerValue(property,property.value??false),})):[];}}
return value;}
__exports.extractInfoFromGroupData=extractInfoFromGroupData;function extractInfoFromGroupData(groupData,groupBy,fields){const info={};const groupByField=fields[groupBy[0].split(":")[0]];info.count=groupData.__count||groupData[`${groupByField.name}_count`];info.length=info.count;info.range=groupData.__range?groupData.__range[groupBy[0]]:null;info.domain=groupData.__domain;info.rawValue=groupData[groupBy[0]];info.value=getValueFromGroupData(groupByField,info.rawValue,info.range);info.displayName=getDisplayNameFromGroupData(groupByField,info.rawValue);info.serverValue=getGroupServerValue(groupByField,info.value);info.aggregates=getAggregatesFromGroupData(groupData,fields);return info;}
function getAggregatesFromGroupData(groupData,fields){const aggregates={};for(const[key,value]of Object.entries(groupData)){if(key in fields&&AGGREGATABLE_FIELD_TYPES.includes(fields[key].type)){aggregates[key]=value;}}
return aggregates;}
function getDisplayNameFromGroupData(field,rawValue){if(field.type==="selection"){return Object.fromEntries(field.selection)[rawValue];}
if(["many2one","many2many","tags"].includes(field.type)){return rawValue?rawValue[1]:false;}
return rawValue;}
__exports.getGroupServerValue=getGroupServerValue;function getGroupServerValue(field,value){switch(field.type){case"many2many":{return value?[value]:false;}
case"datetime":{return value?serializeDateTime(value):false;}
case"date":{return value?serializeDate(value):false;}
default:{return value||false;}}}
function getValueFromGroupData(field,rawValue,range){if(["date","datetime"].includes(field.type)){if(!range){return false;}
const dateValue=parseServerValue(field,range.to);return dateValue.minus({[field.type==="date"?"day":"second"]:1,});}
const value=parseServerValue(field,rawValue);if(["many2one","many2many"].includes(field.type)){return value?value[0]:false;}
return value;}
__exports.fromUnityToServerValues=fromUnityToServerValues;function fromUnityToServerValues(values,fields,activeFields,{withReadonly,context}={}){const{CREATE,UPDATE}=x2ManyCommands;const serverValues={};for(const fieldName in values){let value=values[fieldName];const field=fields[fieldName];const activeField=activeFields[fieldName];if(!withReadonly){if(field.readonly){continue;}
try{if(evaluateExpr(activeField.readonly,context)){continue;}}catch{}}
switch(fields[fieldName].type){case"one2many":case"many2many":value=value.map((c)=>{if(c[0]===CREATE||c[0]===UPDATE){const _fields=activeField.related.fields;const _activeFields=activeField.related.activeFields;return[c[0],c[1],fromUnityToServerValues(c[2],_fields,_activeFields,{withReadonly}),];}
return[c[0],c[1]];});break;case"many2one":value=value?value.id:false;break;}
serverValues[fieldName]=value;}
return serverValues;}
__exports.isRelational=isRelational;function isRelational(field){return field&&["one2many","many2many","many2one"].includes(field.type);}
__exports.useRecordObserver=useRecordObserver;function useRecordObserver(callback){const component=useComponent();let alive=true;let props=component.props;const fct=()=>{const def=new Deferred();let firstCall=true;effect((record)=>{if(firstCall){firstCall=false;return Promise.resolve(callback(record,props)).then(def.resolve).catch(def.reject);}else{return batched((record)=>{if(!alive){return;}
return Promise.resolve(callback(record,props)).then(def.resolve).catch(def.reject);},()=>new Promise((resolve)=>window.requestAnimationFrame(resolve)))(record);}},[props.record]);return def;};onWillDestroy(()=>{alive=false;});onWillStart(()=>fct());onWillUpdateProps((nextProps)=>{const currentRecordId=props.record.id;props=nextProps;if(props.record.id!==currentRecordId){return fct();}});}
__exports.resequence=resequence;async function resequence({records,resModel,orm,fieldName,movedId,targetId,asc=true,getSequence=(record)=>record[fieldName],getResId=(record)=>record.id,context,}){const fromIndex=records.findIndex((d)=>d.id===movedId);let toIndex=0;if(targetId!==null){const targetIndex=records.findIndex((d)=>d.id===targetId);toIndex=fromIndex>targetIndex?targetIndex+1:targetIndex;}
const firstIndex=Math.min(fromIndex,toIndex);const lastIndex=Math.max(fromIndex,toIndex)+1;let reorderAll=records.some((record)=>getSequence(record)===undefined);if(!reorderAll){let lastSequence=(asc?-1:1)*Infinity;for(let index=0;index<records.length;index++){const sequence=getSequence(records[index]);if((asc&&lastSequence>=sequence)||(!asc&&lastSequence<=sequence)){reorderAll=true;break;}
lastSequence=sequence;}}
const originalOrder=[...records];const record=records[fromIndex];if(fromIndex!==toIndex){records.splice(fromIndex,1);records.splice(toIndex,0,record);}
let toReorder=records;if(!reorderAll){toReorder=toReorder.slice(firstIndex,lastIndex).filter((r)=>r.id!==movedId);if(fromIndex<toIndex){toReorder.push(record);}else{toReorder.unshift(record);}}
if(!asc){toReorder.reverse();}
const resIds=toReorder.map((d)=>getResId(d)).filter((id)=>id&&!isNaN(id));const sequences=toReorder.map(getSequence);const offset=sequences.length&&Math.min(...sequences);const params={model:resModel,ids:resIds,context:context,field:fieldName,};if(offset){params.offset=offset;}
try{const wasResequenced=await rpc("/web/dataset/resequence",params);if(!wasResequenced){return;}}catch(error){records.splice(0,records.length,...originalOrder);throw error;}
const kwargs={context};return orm.read(resModel,resIds,[fieldName],kwargs);}
return __exports;});;

/* /web/static/src/model/sample_server.js */
odoo.define('@web/model/sample_server',['@web/core/l10n/dates','@web/core/orm_service','@web/core/registry','@web/core/utils/arrays','@web/model/relational_model/utils'],function(require){'use strict';let __exports={};const{deserializeDate,deserializeDateTime,parseDate,serializeDate,serializeDateTime,}=require("@web/core/l10n/dates");const{ORM}=require("@web/core/orm_service");const{registry}=require("@web/core/registry");const{cartesian,sortBy:arraySortBy}=require("@web/core/utils/arrays");const{parseServerValue}=require("@web/model/relational_model/utils");class UnimplementedRouteError extends Error{}
let searchReadNumber=0;function getSampleFromId(id,sampleTexts){return sampleTexts[(id-1)%sampleTexts.length];}
function serializeGroupDateValue(range,field){if(!range){return false;}
let dateValue=parseServerValue(field,range.to);dateValue=dateValue.minus({[field.type==="date"?"day":"second"]:1,});return field.type==="date"?serializeDate(dateValue):serializeDateTime(dateValue);}
function fieldNameRegex(...terms){return new RegExp(`\\b((\\w+)?_)?(${terms.join("|")})(_(\\w+)?)?\\b`);}
const MEASURE_SPEC_REGEX=/(?<measure>\w+):(?<aggregateFunction>\w+)(\((?<fieldName>\w+)\))?/;const DESCRIPTION_REGEX=fieldNameRegex("description","label","title","subject","message");const EMAIL_REGEX=fieldNameRegex("email");const PHONE_REGEX=fieldNameRegex("phone");const URL_REGEX=fieldNameRegex("url");const SampleServer=__exports.SampleServer=class SampleServer{constructor(modelName,fields){this.mainModel=modelName;this.data={};this.data[modelName]={fields,records:[],};for(const fieldName in fields){const field=fields[fieldName];if(["many2one","one2many","many2many"].includes(field.type)){this.data[field.relation]=this.data[field.relation]||{fields:{display_name:{type:"char"},id:{type:"integer"},color:{type:"integer"},},records:[],};}}
this.existingGroups=null;this.populated=false;this.existingGroupsPopulated=false;}
mockRpc(params){if(!(params.model in this.data)){throw new Error(`SampleServer: unknown model ${params.model}`);}
this._populateModels();switch(params.method||params.route){case"web_search_read":return this._mockWebSearchReadUnity(params);case"web_read_group":return this._mockWebReadGroup(params);case"read_group":return this._mockReadGroup(params);case"read_progress_bar":return this._mockReadProgressBar(params);case"read":return this._mockRead(params);}
const method=params.method||params.route;const mockFunction=registry.category("sample_server").get(`${params.model}/${method}`,null)||registry.category("sample_server").get(method,null);if(mockFunction){return mockFunction.call(this,params);}
console.log(`SampleServer: unimplemented route "${params.method || params.route}"`);throw new SampleServer.UnimplementedRouteError();}
setExistingGroups(groups){this.existingGroups=groups;}
_aggregateFields(measures,records){const values={};for(const{fieldName,type,aggregateFunction}of measures){if(["float","integer","monetary"].includes(type)){if(aggregateFunction==="array_agg"){values[fieldName]=(records||[]).map((r)=>r[fieldName]);}else if(records.length){let value=0;for(const record of records){value+=record[fieldName];}
values[fieldName]=this._sanitizeNumber(value);}else{values[fieldName]=null;}}
if(type==="many2one"){const ids=new Set(records.map((r)=>r[fieldName]));values.fieldName=ids.size||null;}}
return values;}
_formatValue(value,options){if(!value){return false;}
const{type,interval,relation}=options;if(["date","datetime"].includes(type)){const fmt=SampleServer.FORMATS[interval];return parseDate(value).toFormat(fmt);}else if(["many2one","many2many"].includes(type)){const rec=this.data[relation].records.find(({id})=>id===value);return[value,rec.display_name];}else{return value;}}
_generateFieldValue(modelName,fieldName,id){const field=this.data[modelName].fields[fieldName];switch(field.type){case"boolean":return fieldName==="active"?true:this._getRandomBool();case"char":case"text":if(["display_name","name"].includes(fieldName)){if(SampleServer.PEOPLE_MODELS.includes(modelName)){return getSampleFromId(id,SampleServer.SAMPLE_PEOPLE);}else if(modelName==="res.country"){return getSampleFromId(id,SampleServer.SAMPLE_COUNTRIES);}}
if(fieldName==="display_name"){return getSampleFromId(id,SampleServer.SAMPLE_TEXTS);}else if(["name","reference"].includes(fieldName)){return`REF${String(id).padStart(4, "0")}`;}else if(DESCRIPTION_REGEX.test(fieldName)){return getSampleFromId(id,SampleServer.SAMPLE_TEXTS);}else if(EMAIL_REGEX.test(fieldName)){const emailName=getSampleFromId(id,SampleServer.SAMPLE_PEOPLE).replace(/ /,".").toLowerCase();return`${emailName}@sample.demo`;}else if(PHONE_REGEX.test(fieldName)){return`+1 555 754 ${String(id).padStart(4, "0")}`;}else if(URL_REGEX.test(fieldName)){return`http://sample${id}.com`;}
return false;case"date":case"datetime":{const datetime=this._getRandomDate();return field.type==="date"?serializeDate(datetime):serializeDateTime(datetime);}
case"float":return this._getRandomFloat(SampleServer.MAX_FLOAT);case"integer":{let max=SampleServer.MAX_INTEGER;if(fieldName.includes("color")){max=this._getRandomBool()?SampleServer.MAX_COLOR_INT:0;}
return this._getRandomInt(max);}
case"monetary":return this._getRandomInt(SampleServer.MAX_MONETARY);case"many2one":if(field.relation==="res.currency"){return 1;}
if(field.relation==="ir.attachment"){return false;}
return this._getRandomSubRecordId();case"one2many":case"many2many":{const ids=[this._getRandomSubRecordId(),this._getRandomSubRecordId()];return[...new Set(ids)];}
case"selection":{return this._getRandomSelectionValue(modelName,field);}
default:return false;}}
_getRandomArrayEl(array){return array[Math.floor(Math.random()*array.length)];}
_getRandomBool(){return Math.random()<0.5;}
_getRandomDate(){const delta=Math.floor((Math.random()-Math.random())*SampleServer.DATE_DELTA);return luxon.DateTime.local().plus({hours:delta});}
_getRandomFloat(max){return this._sanitizeNumber(Math.random()*max);}
_getRandomInt(max){return Math.floor(Math.random()*max);}
_getRandomSelectionValue(modelName,field){if(field.selection.length>0){return this._getRandomArrayEl(field.selection)[0];}
return false;}
_getRandomSubRecordId(){return Math.floor(Math.random()*SampleServer.SUB_RECORDSET_SIZE)+1;}
_mockRead(params){const model=this.data[params.model];const ids=params.args[0];const fieldNames=params.args[1];const records=[];for(const r of model.records){if(!ids.includes(r.id)){continue;}
const record={id:r.id};for(const fieldName of fieldNames){const field=model.fields[fieldName];if(!field){record[fieldName]=false;}else if(field.type==="many2one"){const relModel=this.data[field.relation];const relRecord=relModel.records.find((relR)=>r[fieldName]===relR.id);record[fieldName]=relRecord?[relRecord.id,relRecord.display_name]:false;}else{record[fieldName]=r[fieldName];}}
records.push(record);}
return records;}
_mockReadGroup(params){const lazy="lazy"in params?params.lazy:true;const model=params.model;const fields=this.data[model].fields;const records=this.data[model].records;const normalizedGroupBys=[];let groupBy=[];if(params.groupBy.length){groupBy=lazy?[params.groupBy[0]]:params.groupBy;}
for(const groupBySpec of groupBy){let[fieldName,interval]=groupBySpec.split(":");interval=interval||"month";const{type,relation}=fields[fieldName];if(type){const gb={fieldName,type,interval,relation,alias:groupBySpec};normalizedGroupBys.push(gb);}}
const groupsFromRecord=(record)=>{const values=[];for(const gb of normalizedGroupBys){const{fieldName,type}=gb;let fieldVals;if(["date","datetime"].includes(type)){fieldVals=[this._formatValue(record[fieldName],gb)];}else if(type==="many2many"){fieldVals=record[fieldName].length?record[fieldName]:[false];}else{fieldVals=[record[fieldName]];}
values.push(fieldVals.map((val)=>({[fieldName]:val})));}
const cart=cartesian(...values);return cart.map((tuple)=>{if(!Array.isArray(tuple)){tuple=[tuple];}
return Object.assign({},...tuple);});};const groups={};for(const record of records){const recordGroups=groupsFromRecord(record);for(const group of recordGroups){const groupId=JSON.stringify(group);if(!(groupId in groups)){groups[groupId]=[];}
groups[groupId].push(record);}}
const measures=[];for(const measureSpec of params.fields||Object.keys(fields)){const matches=measureSpec.match(MEASURE_SPEC_REGEX);let{fieldName,aggregateFunction,measure}=(matches&&matches.groups)||{};if(!aggregateFunction&&fieldName in fields&&fields[fieldName].aggregator){aggregateFunction=fields[fieldName].aggregator;measure=fieldName;}
if(!fieldName&&!measure){continue;}
const fName=fieldName||measure;const{type}=fields[fName];if(!params.groupBy.includes(fName)&&type&&(type!=="many2one"||aggregateFunction!=="count_distinct")){measures.push({fieldName:fName,type,aggregateFunction});}}
let result=[];for(const id in groups){const records=groups[id];const group={__domain:[]};let countKey=`__count`;if(normalizedGroupBys.length&&lazy){countKey=`${normalizedGroupBys[0].fieldName}_count`;}
group[countKey]=records.length;const firstElem=records[0];const parsedId=JSON.parse(id);for(const gb of normalizedGroupBys){const{alias,fieldName,type}=gb;if(type==="many2many"){group[alias]=this._formatValue(parsedId[fieldName],gb);}else{group[alias]=this._formatValue(firstElem[fieldName],gb);if(["date","datetime"].includes(type)){group.__range={};const val=firstElem[fieldName];if(val){const deserialize=type==="date"?deserializeDate:deserializeDateTime;const serialize=type==="date"?serializeDate:serializeDateTime;const from=deserialize(val).startOf(gb.interval);const to=SampleServer.INTERVALS[gb.interval](from);group.__range[alias]={from:serialize(from),to:serialize(to)};}else{group.__range[alias]=false;}}}}
Object.assign(group,this._aggregateFields(measures,records));result.push(group);}
if(normalizedGroupBys.length>0){const{alias,interval,type}=normalizedGroupBys[0];result=arraySortBy(result,(group)=>{const val=group[alias];if(["date","datetime"].includes(type)){return parseDate(val,{format:SampleServer.FORMATS[interval]});}
return val;});}
return result;}
_mockReadProgressBar(params){const groupBy=params.group_by.split(":")[0];const progress_bar=params.progress_bar;const groupByField=this.data[params.model].fields[groupBy];const data={};for(const record of this.data[params.model].records){let groupByValue=record[groupBy];if(groupByField.type==="many2one"){const relatedRecords=this.data[groupByField.relation].records;const relatedRecord=relatedRecords.find((r)=>r.id===groupByValue);groupByValue=relatedRecord.display_name;}
if(!(groupByValue in data)){if(groupByValue===true){groupByValue="True";}else if(groupByValue===false){groupByValue="False";}}
if(!(groupByValue in data)){data[groupByValue]={};for(const key in progress_bar.colors){data[groupByValue][key]=0;}}
const fieldValue=record[progress_bar.field];if(fieldValue in data[groupByValue]){data[groupByValue][fieldValue]++;}}
return data;}
_mockWebSearchReadUnity(params){const fields=Object.keys(params.specification);let result;if(this.existingGroups){const groups=this.existingGroups;const group=groups[searchReadNumber++%groups.length];result={records:this._mockRead({model:params.model,args:[group.__recordIds,fields],}),length:group.__recordIds.length,};}else{const model=this.data[params.model];const rawRecords=model.records.slice(0,SampleServer.SEARCH_READ_LIMIT);const records=this._mockRead({model:params.model,args:[rawRecords.map((r)=>r.id),fields],});result={records,length:records.length};}
for(const fieldName in params.specification){const field=this.data[params.model].fields[fieldName];if(field.type==="many2one"){for(const record of result.records){record[fieldName]=record[fieldName]?{id:record[fieldName][0],display_name:record[fieldName][1],}:false;}}
if(field.type==="one2many"||field.type==="many2many"){const relFields=Object.keys(params.specification[fieldName].fields||{});if(relFields.length){const relIds=result.records.map((r)=>r[fieldName]).flat();const relRecords={};const _relRecords=this._mockRead({model:field.relation,args:[relIds,relFields],});for(const relRecord of _relRecords){relRecords[relRecord.id]=relRecord;}
for(const record of result.records){record[fieldName]=record[fieldName].map((resId)=>relRecords[resId]);}}}}
return result;}
_mockWebReadGroup(params){let groups;if(this.existingGroups){this._tweakExistingGroups(params);groups=this.existingGroups;}else{groups=this._mockReadGroup(params);}
return{groups,length:groups.length,};}
_populateExistingGroups(params){const groups=this.existingGroups;const groupBy=params.groupBy[0].split(":")[0];const groupByField=this.data[params.model].fields[groupBy];const groupedByM2O=groupByField.type==="many2one";if(groupedByM2O){this.data[groupByField.relation].records=groups.map((g)=>{return{id:g[groupBy][0],display_name:g[groupBy][1]};});}
for(const r of this.data[params.model].records){const group=getSampleFromId(r.id,groups);if(["date","datetime"].includes(groupByField.type)){r[groupBy]=serializeGroupDateValue(group.__range[params.groupBy[0]],groupByField);}else if(groupByField.type==="many2one"){r[groupBy]=group[params.groupBy[0]]?group[params.groupBy[0]][0]:false;}else{r[groupBy]=group[params.groupBy[0]];}}}
_populateModels(){if(!this.populated){for(const modelName in this.data){const model=this.data[modelName];const fieldNames=Object.keys(model.fields).filter((f)=>f!=="id");const size=modelName===this.mainModel?SampleServer.MAIN_RECORDSET_SIZE:SampleServer.SUB_RECORDSET_SIZE;for(let id=1;id<=size;id++){const record={id};for(const fieldName of fieldNames){record[fieldName]=this._generateFieldValue(modelName,fieldName,id);}
model.records.push(record);}}
this.populated=true;}}
_sanitizeNumber(value){return parseFloat(value.toFixed(SampleServer.FLOAT_PRECISION));}
_tweakExistingGroups(params){const groups=this.existingGroups;this._populateExistingGroups(params);const fullGroupBy=params.groupBy[0];const groupBy=fullGroupBy.split(":")[0];const groupByField=this.data[params.model].fields[groupBy];const records=this.data[params.model].records;const fields=params.fields.map((aggregate_spec)=>aggregate_spec.split(":")[0])
for(const g of groups){const recordsInGroup=records.filter((r)=>{if(["date","datetime"].includes(groupByField.type)){return(r[groupBy]===serializeGroupDateValue(g.__range[fullGroupBy],groupByField));}else if(groupByField.type==="many2one"){return(!r[groupBy]&&!g[fullGroupBy])||r[groupBy]===g[fullGroupBy][0];}
return r[groupBy]===g[fullGroupBy];});for(const field of fields){const fieldType=this.data[params.model].fields[field].type;if(["integer, float","monetary"].includes(fieldType)){g[field]=recordsInGroup.reduce((acc,r)=>acc+r[field],0);}}
g[`${groupBy}_count`]=recordsInGroup.length;g.__recordIds=recordsInGroup.map((r)=>r.id);}}}
SampleServer.FORMATS={day:"yyyy-MM-dd",week:"'W'WW kkkk",month:"MMMM yyyy",quarter:"'Q'q yyyy",year:"y",};SampleServer.INTERVALS={day:(dt)=>dt.plus({days:1}),week:(dt)=>dt.plus({weeks:1}),month:(dt)=>dt.plus({months:1}),quarter:(dt)=>dt.plus({months:3}),year:(dt)=>dt.plus({years:1}),};SampleServer.DISPLAY_FORMATS=Object.assign({},SampleServer.FORMATS,{day:"dd MMM yyyy"});SampleServer.MAIN_RECORDSET_SIZE=16;SampleServer.SUB_RECORDSET_SIZE=5;SampleServer.SEARCH_READ_LIMIT=10;SampleServer.MAX_FLOAT=100;SampleServer.MAX_INTEGER=50;SampleServer.MAX_COLOR_INT=7;SampleServer.MAX_MONETARY=100000;SampleServer.DATE_DELTA=24*60;SampleServer.FLOAT_PRECISION=2;SampleServer.SAMPLE_COUNTRIES=["Belgium","France","Portugal","Singapore","Australia"];SampleServer.SAMPLE_PEOPLE=["John Miller","Henry Campbell","Carrie Helle","Wendi Baltz","Thomas Passot",];SampleServer.SAMPLE_TEXTS=["Laoreet id","Volutpat blandit","Integer vitae","Viverra nam","In massa",];SampleServer.PEOPLE_MODELS=["res.users","res.partner","hr.employee","mail.followers","mailing.contact",];SampleServer.UnimplementedRouteError=UnimplementedRouteError;__exports.buildSampleORM=buildSampleORM;function buildSampleORM(resModel,fields,user){const sampleServer=new SampleServer(resModel,fields);const fakeRPC=async(_,params)=>{const{args,kwargs,method,model}=params;const{groupby:groupBy}=kwargs;return sampleServer.mockRpc({method,model,args,...kwargs,groupBy});};const sampleORM=new ORM(user);sampleORM.rpc=fakeRPC;sampleORM.isSample=true;sampleORM.setGroups=(groups)=>sampleServer.setExistingGroups(groups);return sampleORM;}
return __exports;});;

/* /web/static/src/search/action_hook.js */
odoo.define('@web/search/action_hook',['@odoo/owl'],function(require){'use strict';let __exports={};const{onMounted,useComponent,useEffect,useExternalListener}=require("@odoo/owl");const scrollSymbol=__exports.scrollSymbol=Symbol("scroll");const CallbackRecorder=__exports.CallbackRecorder=class CallbackRecorder{constructor(){this.setup();}
setup(){this._callbacks=[];}
get callbacks(){return this._callbacks.map(({callback})=>callback);}
add(owner,callback){if(!callback){throw new Error("Missing callback");}
this._callbacks.push({owner,callback});}
remove(owner){this._callbacks=this._callbacks.filter((s)=>s.owner!==owner);}}
__exports.useCallbackRecorder=useCallbackRecorder;function useCallbackRecorder(callbackRecorder,callback){const component=useComponent();useEffect(()=>{callbackRecorder.add(component,callback);return()=>callbackRecorder.remove(component);},()=>[]);}
__exports.useSetupAction=useSetupAction;function useSetupAction(params={}){const component=useComponent();const{__beforeLeave__,__getGlobalState__,__getLocalState__,__getContext__,__getOrderBy__,}=component.env;const{beforeVisibilityChange,beforeUnload,beforeLeave,getGlobalState,getLocalState,rootRef,}=params;if(beforeVisibilityChange){useExternalListener(document,"visibilitychange",beforeVisibilityChange);}
if(beforeUnload){useExternalListener(window,"beforeunload",beforeUnload);}
if(__beforeLeave__&&beforeLeave){useCallbackRecorder(__beforeLeave__,beforeLeave);}
if(__getGlobalState__&&(getGlobalState||rootRef)){useCallbackRecorder(__getGlobalState__,()=>{const state={};if(getGlobalState){Object.assign(state,getGlobalState());}
return state;});}
if(__getLocalState__&&(getLocalState||rootRef)){useCallbackRecorder(__getLocalState__,()=>{const state={};if(getLocalState){Object.assign(state,getLocalState());}
if(rootRef){if(component.env.isSmall){state[scrollSymbol]={root:{left:rootRef.el.scrollLeft,top:rootRef.el.scrollTop},};}else{const contentEl=rootRef.el.querySelector(".o_component_with_search_panel > .o_renderer_with_searchpanel,"+".o_component_with_search_panel > .o_renderer")||rootRef.el.querySelector(".o_content");if(contentEl){state[scrollSymbol]={content:{left:contentEl.scrollLeft,top:contentEl.scrollTop},};}}}
return state;});if(rootRef){onMounted(()=>{const{state}=component.props;const scrolling=state&&state[scrollSymbol];if(scrolling){if(component.env.isSmall){rootRef.el.scrollTop=(scrolling.root&&scrolling.root.top)||0;rootRef.el.scrollLeft=(scrolling.root&&scrolling.root.left)||0;}else if(scrolling.content){const contentEl=rootRef.el.querySelector(".o_component_with_search_panel > .o_renderer_with_searchpanel,"+".o_component_with_search_panel > .o_renderer")||rootRef.el.querySelector(".o_content");if(contentEl){contentEl.scrollTop=scrolling.content.top||0;contentEl.scrollLeft=scrolling.content.left||0;}}}});}}
if(__getContext__&&params.getContext){useCallbackRecorder(__getContext__,params.getContext);}
if(__getOrderBy__&&params.getOrderBy){useCallbackRecorder(__getOrderBy__,params.getOrderBy);}}
return __exports;});;

/* /web/static/src/search/action_menus/action_menus.js */
odoo.define('@web/search/action_menus/action_menus',['@web/core/browser/browser','@web/core/context','@web/session','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_item','@web/core/l10n/translation','@web/core/utils/hooks','@odoo/owl'],function(require){'use strict';let __exports={};const{browser}=require("@web/core/browser/browser");const{makeContext}=require("@web/core/context");const{session}=require("@web/session");const{Dropdown}=require("@web/core/dropdown/dropdown");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{Component,onWillStart,onWillUpdateProps,useState}=require("@odoo/owl");const STATIC_ACTIONS_GROUP_NUMBER=__exports.STATIC_ACTIONS_GROUP_NUMBER=1;const ACTIONS_GROUP_NUMBER=__exports.ACTIONS_GROUP_NUMBER=100;const ActionMenus=__exports.ActionMenus=class ActionMenus extends Component{static template="web.ActionMenus";static components={Dropdown,DropdownItem,};static props={getActiveIds:Function,context:Object,resModel:String,printDropdownTitle:{type:String,optional:true},domain:{type:Array,optional:true},isDomainSelected:{type:Boolean,optional:true},items:{type:Object,shape:{action:{type:Array,optional:true},print:{type:Array,optional:true},},},onActionExecuted:{type:Function,optional:true},shouldExecuteAction:{type:Function,optional:true},loadExtraPrintItems:{type:Function,optional:true},};static defaultProps={printDropdownTitle:_t("Print"),onActionExecuted:()=>{},shouldExecuteAction:()=>true,loadExtraPrintItems:()=>[],};setup(){this.orm=useService("orm");this.actionService=useService("action");this.state=useState({printItems:[]})
onWillStart(async()=>{this.actionItems=await this.getActionItems(this.props);});onWillUpdateProps(async(nextProps)=>{this.actionItems=await this.getActionItems(nextProps);});}
async getActionItems(props){return(props.items.action||[]).map((action)=>{if(action.callback){return Object.assign({key:`action-${action.description}`,groupNumber:ACTIONS_GROUP_NUMBER},action);}else{return{action,description:action.name,key:action.id,groupNumber:action.groupNumber||ACTIONS_GROUP_NUMBER,};}});}
async executeAction(action){let activeIds=this.props.getActiveIds();if(this.props.isDomainSelected){activeIds=await this.orm.search(this.props.resModel,this.props.domain,{limit:session.active_ids_limit,context:this.props.context,});}
const activeIdsContext={active_id:activeIds[0],active_ids:activeIds,active_model:this.props.resModel,};if(this.props.domain){activeIdsContext.active_domain=this.props.domain;}
const context=makeContext([this.props.context,activeIdsContext]);return this.actionService.doAction(action.id,{additionalContext:context,onClose:this.props.onActionExecuted,});}
async onItemSelected(item){if(!(await this.props.shouldExecuteAction(item))){return;}
if(item.callback){item.callback([item]);}else if(item.action){this.executeAction(item.action);}else if(item.url){browser.location=item.url;}}
async loadAvailablePrintItems(){const printActions=this.props.items.print||[];const actionWithDomainIds=[];const validActionIds=[];for(const action of printActions){"domain"in action?actionWithDomainIds.push(action.id):validActionIds.push(action.id);}
if(actionWithDomainIds.length){const validActionsWithDomainIds=await this.orm.call("ir.actions.report","get_valid_action_reports",[actionWithDomainIds,this.props.resModel,this.props.getActiveIds()]);validActionIds.push(...validActionsWithDomainIds);}
return printActions.filter((action)=>validActionIds.includes(action.id)).map((action)=>({action,class:"o_menu_item",description:action.name,key:action.id,}));}
async loadPrintItems(){if(!this.props.items.print?.length){return;}
const[items,extraItems]=await Promise.all([this.loadAvailablePrintItems(),this.props.loadExtraPrintItems(),]);const allItems=[...extraItems,...items];if(!allItems.length){allItems.push({description:_t("No report available."),class:"o_menu_item disabled",key:"nothing_to_display",});}
this.state.printItems=allItems;}}
return __exports;});;

/* /web/static/src/search/breadcrumbs/breadcrumbs.js */
odoo.define('@web/search/breadcrumbs/breadcrumbs',['@odoo/owl','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_item','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{Dropdown}=require("@web/core/dropdown/dropdown");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{_t}=require("@web/core/l10n/translation");const Breadcrumbs=__exports.Breadcrumbs=class Breadcrumbs extends Component{static template="web.Breadcrumbs";static components={Dropdown,DropdownItem};static props={breadcrumbs:Array,slots:{type:Object,optional:true},};getBreadcrumbTooltip({isFormView,name}){if(isFormView){return _t("Back to “%s” form",name);}
return _t("Back to “%s”",name);}}
return __exports;});;

/* /web/static/src/search/cog_menu/cog_menu.js */
odoo.define('@web/search/cog_menu/cog_menu',['@web/core/registry','@web/core/dropdown/dropdown','@web/search/action_menus/action_menus','@odoo/owl'],function(require){'use strict';let __exports={};const{registry}=require("@web/core/registry");const{Dropdown}=require("@web/core/dropdown/dropdown");const{ActionMenus}=require("@web/search/action_menus/action_menus");const{onWillStart,onWillUpdateProps}=require("@odoo/owl");const cogMenuRegistry=registry.category("cogMenu");const CogMenu=__exports.CogMenu=class CogMenu extends ActionMenus{static template="web.CogMenu";static components={...ActionMenus.components,Dropdown,};static props={...ActionMenus.props,getActiveIds:{type:ActionMenus.props.getActiveIds,optional:true},context:{type:ActionMenus.props.context,optional:true},resModel:{type:ActionMenus.props.resModel,optional:true},items:{...ActionMenus.props.items,optional:true},};static defaultProps={...ActionMenus.defaultProps,items:{},};setup(){super.setup();onWillStart(async()=>{this.registryItems=await this._registryItems();});onWillUpdateProps(async()=>{this.registryItems=await this._registryItems();});}
get hasItems(){return this.cogItems.length||this.props.items.print?.length;}
async _registryItems(){const items=[];for(const item of cogMenuRegistry.getAll()){if("isDisplayed"in item?await item.isDisplayed(this.env):true){items.push({Component:item.Component,groupNumber:item.groupNumber,key:item.Component.name,});}}
return items;}
get cogItems(){return[...this.actionItems,...this.registryItems].sort((item1,item2)=>{const grp=(item1.groupNumber||0)-(item2.groupNumber||0);if(grp!==0){return grp;}
return(item1.sequence||0)-(item2.sequence||0);});}}
return __exports;});;

/* /web/static/src/search/control_panel/control_panel.js */
odoo.define('@web/search/control_panel/control_panel',['@web/core/l10n/translation','@web/core/browser/browser','@web/core/hotkeys/hotkey_service','@web/core/pager/pager','@web/core/utils/hooks','@web/core/dropdown/dropdown','@web/core/commands/command_hook','@web/core/dropdown/dropdown_item','@web/core/hotkeys/hotkey_hook','@web/core/utils/sortable_owl','@web/core/user','@web/core/dropdown/accordion_item','@web/core/checkbox/checkbox','@web/core/context','@web/core/confirmation_dialog/confirmation_dialog','@web/core/transition','@web/search/breadcrumbs/breadcrumbs','@web/search/search_bar/search_bar','@odoo/owl'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{browser}=require("@web/core/browser/browser");const{getActiveHotkey}=require("@web/core/hotkeys/hotkey_service");const{Pager}=require("@web/core/pager/pager");const{useService}=require("@web/core/utils/hooks");const{Dropdown}=require("@web/core/dropdown/dropdown");const{useCommand}=require("@web/core/commands/command_hook");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{useHotkey}=require("@web/core/hotkeys/hotkey_hook");const{useSortable}=require("@web/core/utils/sortable_owl");const{user}=require("@web/core/user");const{AccordionItem}=require("@web/core/dropdown/accordion_item");const{CheckBox}=require("@web/core/checkbox/checkbox");const{makeContext}=require("@web/core/context");const{ConfirmationDialog}=require("@web/core/confirmation_dialog/confirmation_dialog");const{Transition}=require("@web/core/transition");const{Breadcrumbs}=require("@web/search/breadcrumbs/breadcrumbs");const{SearchBar}=require("@web/search/search_bar/search_bar");const{Component,useState,onMounted,useExternalListener,useRef,useEffect}=require("@odoo/owl");const STICKY_CLASS="o_mobile_sticky";const ControlPanel=__exports.ControlPanel=class ControlPanel extends Component{static template="web.ControlPanel";static components={Pager,SearchBar,Dropdown,DropdownItem,Breadcrumbs,AccordionItem,CheckBox,Transition,};static props={display:{type:Object,optional:true},slots:{type:Object,optional:true},};setup(){this.actionService=useService("action");this.pagerProps=this.env.config.pagerProps?useState(this.env.config.pagerProps):undefined;this.notificationService=useService("notification");this.breadcrumbs=useState(this.env.config.breadcrumbs);this.orm=useService("orm");this.dialogService=useService("dialog");this.root=useRef("root");this.newActionNameRef=useRef("newActionNameRef");this.isEmbeddedActionsOrderModifiable=false;this.defaultEmbeddedActions=this.env.config.embeddedActions;if(this.env.config.embeddedActions?.length>0&&!this.env.config.parentActionId){const{parent_res_model,parent_action_id}=this.env.config.embeddedActions[0];this.defaultEmbeddedActions=[{id:false,name:this.env.config?.actionName,parent_action_id,parent_res_model,action_id:parent_action_id,user_id:false,context:{},},...this.env.config.embeddedActions,];this.env.config.setEmbeddedActions(this.defaultEmbeddedActions);}
const parentActionId=this.env.config.parentActionId||this.env.config.embeddedActions?.[0]?.parent_action_id[0]||this.env.config.embeddedActions?.[0]?.parent_action_id||"";this.embeddedActionsVisibilityKey=`visibleEmbeddedActions${parentActionId}+${
            this.env.searchModel?.globalContext.active_id || ""
        }+${user.userId}`;this.embeddedVisibilityKey=`visibleEmbedded${parentActionId}+${
            this.env.searchModel?.globalContext.active_id || ""
        }+${user.userId}`;this.embeddedOrderKey=`orderEmbedded${parentActionId}+${
            this.env.searchModel?.globalContext.active_id || ""
        }+${user.userId}`;this.state=useState({showSearchBar:false,showMobileSearch:false,showViewSwitcher:false,embeddedInfos:{showEmbedded:this.env.config.embeddedActions?.length>0&&((!!this.env.config.parentActionId&&!!JSON.parse(browser.localStorage.getItem("showEmbeddedActions")))||!!JSON.parse(browser.localStorage.getItem(this.embeddedVisibilityKey))),embeddedActions:this.defaultEmbeddedActions||[],newActionIsShared:false,newActionName:this.newActionNameGetter,visibleEmbeddedActions:(this.env.config.embeddedActions?.length>0&&JSON.parse(browser.localStorage.getItem(this.embeddedActionsVisibilityKey)))||{},currentEmbeddedAction:this.currentEmbeddedAction,},});this.onScrollThrottledBound=this.onScrollThrottled.bind(this);const{viewSwitcherEntries,viewType}=this.env.config;for(const view of viewSwitcherEntries||[]){useCommand(_t("Show %s view",view.name),()=>this.switchView(view.type),{category:"view_switcher",isAvailable:()=>view.type!==viewType,});}
if(viewSwitcherEntries?.length>1){useHotkey("alt+shift+v",()=>{this.cycleThroughViews();},{bypassEditableProtection:true,withOverlay:()=>this.root.el.querySelector("nav.o_cp_switch_buttons"),});}
useExternalListener(window,"click",this.onWindowClick);useEffect(()=>{if(!this.env.isSmall||("adaptToScroll"in this.display&&!this.display.adaptToScroll)){return;}
const scrollingEl=this.getScrollingElement();this.scrollingElementResizeObserver.observe(scrollingEl);scrollingEl.addEventListener("scroll",this.onScrollThrottledBound);this.root.el.style.top="0px";this.scrollingElementHeight=scrollingEl.scrollHeight;return()=>{this.scrollingElementResizeObserver.unobserve(scrollingEl);scrollingEl.removeEventListener("scroll",this.onScrollThrottledBound);};});onMounted(async()=>{if(this.state.embeddedInfos.embeddedActions?.length>0){const embeddedActionKey=this.state.embeddedInfos.currentEmbeddedAction?.id||false;if(!Object.keys(this.state.embeddedInfos.visibleEmbeddedActions).includes(embeddedActionKey.toString())){this._setVisibility(embeddedActionKey);}
const embeddedOrderLocalStorageKey=browser.localStorage.getItem(this.embeddedOrderKey);if(embeddedOrderLocalStorageKey){this._sortEmbeddedActions(JSON.parse(embeddedOrderLocalStorageKey));}}
if(!this.env.isSmall||("adaptToScroll"in this.display&&!this.display.adaptToScroll)){return;}
this.oldScrollTop=0;this.lastScrollTop=0;this.initialScrollTop=this.getScrollingElement().scrollTop;});this.mainButtons=useRef("mainButtons");useEffect(()=>{const dropdownButtons=this.mainButtons.el.querySelectorAll(".o_control_panel_collapsed_create.dropdown-menu button");if(!dropdownButtons.length){this.mainButtons.el.querySelectorAll(".o_control_panel_collapsed_create.dropdown-menu, .o_control_panel_collapsed_create.dropdown-toggle").forEach((el)=>el.classList.add("d-none"));this.mainButtons.el.querySelectorAll(".o_control_panel_collapsed_create.btn-group").forEach((el)=>el.classList.remove("btn-group"));return;}
for(const button of dropdownButtons){for(const cl of Array.from(button.classList)){button.classList.toggle(cl,!cl.startsWith("btn-"));}
button.classList.add("dropdown-item","btn","btn-link");}});useSortable({enable:true,ref:this.root,elements:".o_draggable",cursor:"move",delay:200,tolerance:10,onWillStartDrag:(params)=>this._sortEmbeddedActionStart(params),onDrop:(params)=>this._sortEmbeddedActionDrop(params),});}
scrollingElementResizeObserver=new ResizeObserver((entries)=>{for(const entry of entries){if(this.scrollingElementHeight!==entry.target.scrollingElementHeight){this.oldScrollTop+=entry.target.scrollingElementHeight-this.scrollingElementHeight;this.scrollingElementHeight=entry.target.scrollingElementHeight;}}});getDropdownClass(action){return(!this.env.isSmall&&this._checkValueLocalStorage(action))||(this.env.isSmall&&this.state.embeddedInfos.currentEmbeddedAction?.id===action.id)?"selected":"";}
getScrollingElement(){return this.root.el.parentElement;}
get currentEmbeddedAction(){if(!this.env.config){return{};}
const{currentEmbeddedActionId}=this.env.config;return(this.defaultEmbeddedActions?.find(({id})=>id===currentEmbeddedActionId)||this.defaultEmbeddedActions?.[0]);}
get newActionNameGetter(){if(this.currentEmbeddedAction?.name){return _t("Custom %s",this.currentEmbeddedAction.name);}else{return _t("Custom Embedded Action");}}
resetSearchState(){Object.assign(this.state,{showSearchBar:false,showMobileSearch:false,showViewSwitcher:false,});}
get display(){return{layoutActions:true,...this.props.display,};}
onClickShowEmbedded(){if(this.state.embeddedInfos.showEmbedded){browser.localStorage.removeItem(this.embeddedVisibilityKey);}else{browser.localStorage.setItem(this.embeddedVisibilityKey,true);}
this.state.embeddedInfos.showEmbedded=!this.state.embeddedInfos.showEmbedded;browser.localStorage.setItem("showEmbeddedActions",this.state.embeddedInfos.showEmbedded);}
onScrollThrottled(){if(this.isScrolling){return;}
this.isScrolling=true;browser.requestAnimationFrame(()=>(this.isScrolling=false));const scrollTop=this.getScrollingElement().scrollTop;const delta=Math.round(scrollTop-this.oldScrollTop);if(scrollTop>this.initialScrollTop){this.root.el.classList.add(STICKY_CLASS);if(delta<=0){this.lastScrollTop=Math.min(0,this.lastScrollTop-delta);}else{this.lastScrollTop=Math.max(-this.root.el.offsetHeight,-this.root.el.offsetTop-delta);}
this.root.el.style.top=`${this.lastScrollTop}px`;}else{this.root.el.classList.remove(STICKY_CLASS);this.lastScrollTop=0;}
this.oldScrollTop=scrollTop;}
switchView(viewType){this.resetSearchState();this.actionService.switchView(viewType);}
cycleThroughViews(){const currentViewType=this.env.config.viewType;const viewSwitcherEntries=this.env.config.viewSwitcherEntries;const currentIndex=viewSwitcherEntries.findIndex((entry)=>entry.type===currentViewType);const nextIndex=(currentIndex+1)%viewSwitcherEntries.length;this.switchView(viewSwitcherEntries[nextIndex].type);}
onWindowClick(ev){if(this.state.showViewSwitcher&&!ev.target.closest(".o_cp_switch_buttons")){this.state.showViewSwitcher=false;}}
onMainButtonsKeydown(ev){const hotkey=getActiveHotkey(ev);if(hotkey==="arrowdown"){this.env.searchModel.trigger("focus-view");ev.preventDefault();ev.stopPropagation();}}
_checkValueLocalStorage(action){const actionIdStr=action.id.toString();return this.state.embeddedInfos.visibleEmbeddedActions[actionIdStr];}
_setVisibility(actionId){const actionIdStr=actionId.toString();if(this.state.embeddedInfos.visibleEmbeddedActions[actionIdStr]){delete this.state.embeddedInfos.visibleEmbeddedActions[actionIdStr];}else{this.state.embeddedInfos.visibleEmbeddedActions[actionIdStr]=true;}
browser.localStorage.setItem(this.embeddedActionsVisibilityKey,JSON.stringify(this.state.embeddedInfos.visibleEmbeddedActions));}
_onShareCheckboxChange(){this.state.embeddedInfos.newActionIsShared=!this.state.embeddedInfos.newActionIsShared;}
async _saveNewAction(ev){const{newActionName,newActionIsShared,embeddedActions,currentEmbeddedAction,visibleEmbeddedActions,}=this.state.embeddedInfos;if(!newActionName){this.notificationService.add(_t("A name for your new action is required."),{type:"danger",});ev.stopPropagation();return this.newActionNameRef.el.focus();}
const duplicateName=embeddedActions.some(({name})=>name===newActionName);if(duplicateName){this.notificationService.add(_t("An action with the same name already exists."),{type:"danger",});ev.stopPropagation();return this.newActionNameRef.el.focus();}
const userId=newActionIsShared?false:user.userId;const{parent_action_id,action_id,parent_res_model,python_method,domain,context,groups_ids,}=currentEmbeddedAction;const values={parent_action_id:parent_action_id[0],parent_res_model,parent_res_id:this.env.searchModel.globalContext.active_id,user_id:userId,is_deletable:true,default_view_mode:this.env.config.viewType,domain,context,groups_ids,name:newActionName,};if(python_method){values.python_method=python_method;}else{values.action_id=action_id[0]||this.env.config.actionId;}
const embeddedActionId=await this.orm.create("ir.embedded.actions",[values]);const description=`${newActionName}`;this.env.searchModel.createNewFavorite({description,isDefault:true,isShared:newActionIsShared,embeddedActionId:embeddedActionId[0],});Object.assign(this.state.embeddedInfos,{newActionName:"",newActionIsShared:false,});const enrichedNewEmbeddedAction={...values,parent_action_id,action_id,id:embeddedActionId[0],};this.state.embeddedInfos.embeddedActions.push(enrichedNewEmbeddedAction);const embeddedActionIdStr=embeddedActionId[0].toString();visibleEmbeddedActions[embeddedActionIdStr]=true;const order=this.state.embeddedInfos.embeddedActions.map((el)=>el.id);browser.localStorage.setItem(this.embeddedActionsVisibilityKey,JSON.stringify(visibleEmbeddedActions));browser.localStorage.setItem(this.embeddedOrderKey,JSON.stringify(order));this.env.config.setCurrentEmbeddedAction(embeddedActionId);this.state.embeddedInfos.currentEmbeddedAction=enrichedNewEmbeddedAction;this.state.embeddedInfos.newActionName=`${newActionName} Custom`;}
openConfirmationDialog(action){const dialogProps={title:_t("Warning"),body:action.user_id?_t("Are you sure that you want to remove this embedded action?"):_t("This embedded action is global and will be removed for everyone."),confirmLabel:_t("Delete"),confirm:async()=>await this._deleteEmbeddedAction(action),cancel:()=>{},};this.dialogService.add(ConfirmationDialog,dialogProps);}
async _deleteEmbeddedAction(action){const{visibleEmbeddedActions,embeddedActions,currentEmbeddedAction}=this.state.embeddedInfos;const actionIdStr=action.id.toString();if(visibleEmbeddedActions[actionIdStr]){delete visibleEmbeddedActions[actionIdStr];}
browser.localStorage.setItem(this.embeddedActionsVisibilityKey,JSON.stringify(visibleEmbeddedActions));this.state.embeddedInfos.embeddedActions=embeddedActions.filter(({id})=>id!==action.id);await this.orm.unlink("ir.embedded.actions",[action.id]);if(action.id===currentEmbeddedAction?.id){const{active_id,active_model}=this.env.searchModel.globalContext;const actionContext=action.context?makeContext([action.context]):{};const additionalContext={...actionContext,active_id,active_model,};this.actionService.doAction(action.parent_action_id[0]||action.parent_action_id,{additionalContext,stackPosition:"replaceCurrentAction",});}}
async onEmbeddedActionClick(action){this.env.config.setEmbeddedActions(this.state.embeddedInfos.embeddedActions);const{active_id,active_model}=this.env.searchModel.globalContext;const actionContext=action.context?makeContext([action.context]):{};const context={...actionContext,active_id,active_model,current_embedded_action_id:action.id,parent_action_embedded_actions:this.state.embeddedInfos.embeddedActions,parent_action_id:action.parent_action_id[0]||action.parent_action_id,};this.actionService.doActionButton({type:action.python_method?"object":"action",resId:this.env.searchModel?.globalContext.active_id,name:action.python_method||action.action_id[0]||action.action_id,resModel:action.parent_res_model,context,stackPosition:"replaceCurrentAction",viewType:action.default_view_mode,},{isEmbeddedAction:true});}
_sortEmbeddedActions(order){this.state.embeddedInfos.embeddedActions=this.state.embeddedInfos.embeddedActions.sort((a,b)=>{const indexA=order.indexOf(a.id);if(!indexA){return-1;}
const indexB=order.indexOf(b.id);if(!indexB){return 1;}
return indexA-indexB;});}
_sortEmbeddedActionStart({element,addClass}){addClass(element,"o_dragged_embedded_action");}
_sortEmbeddedActionDrop({element,previous}){const order=this.state.embeddedInfos.embeddedActions.map((el)=>el.id);const elementId=Number(element.dataset.id)||false;const elementIndex=order.indexOf(elementId);order.splice(elementIndex,1);if(previous){const prevIndex=order.indexOf(Number(previous.dataset.id)||false);order.splice(prevIndex+1,0,elementId);}else{order.splice(0,0,elementId);}
this._sortEmbeddedActions(order);browser.localStorage.setItem(this.embeddedOrderKey,JSON.stringify(order));}}
return __exports;});;

/* /web/static/src/search/custom_favorite_item/custom_favorite_item.js */
odoo.define('@web/search/custom_favorite_item/custom_favorite_item',['@web/core/l10n/translation','@web/core/dropdown/accordion_item','@web/core/checkbox/checkbox','@web/core/registry','@web/core/utils/hooks','@odoo/owl'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{AccordionItem}=require("@web/core/dropdown/accordion_item");const{CheckBox}=require("@web/core/checkbox/checkbox");const{registry}=require("@web/core/registry");const{useService}=require("@web/core/utils/hooks");const{Component,useRef,useState}=require("@odoo/owl");const favoriteMenuRegistry=registry.category("favoriteMenu");const CustomFavoriteItem=__exports.CustomFavoriteItem=class CustomFavoriteItem extends Component{static template="web.CustomFavoriteItem";static components={CheckBox,AccordionItem};static props={};setup(){this.notificationService=useService("notification");this.descriptionRef=useRef("description");this.state=useState({description:this.env.config.getDisplayName(),isDefault:false,isShared:false,});}
saveFavorite(ev){if(!this.state.description){this.notificationService.add(_t("A name for your favorite filter is required."),{type:"danger",});ev.stopPropagation();return this.descriptionRef.el.focus();}
const favorites=this.env.searchModel.getSearchItems((s)=>s.type==="favorite"&&s.description===this.state.description);if(favorites.length){this.notificationService.add(_t("A filter with same name already exists."),{type:"danger",});ev.stopPropagation();return this.descriptionRef.el.focus();}
const{description,isDefault,isShared}=this.state;const embeddedActionId=this.env.config.currentEmbeddedActionId||false;this.env.searchModel.createNewFavorite({description,isDefault,isShared,embeddedActionId,});Object.assign(this.state,{description:this.env.config.getDisplayName(),isDefault:false,isShared:false,});}
onDefaultCheckboxChange(checked){this.state.isDefault=checked;if(checked){this.state.isShared=false;}}
onShareCheckboxChange(checked){this.state.isShared=checked;if(checked){this.state.isDefault=false;}}
onInputKeydown(ev){switch(ev.key){case"Enter":ev.preventDefault();this.saveFavorite(ev);break;case"Escape":ev.preventDefault();ev.target.blur();break;}}}
favoriteMenuRegistry.add("custom-favorite-item",{Component:CustomFavoriteItem,groupNumber:3},{sequence:0});return __exports;});;

/* /web/static/src/search/custom_group_by_item/custom_group_by_item.js */
odoo.define('@web/search/custom_group_by_item/custom_group_by_item',['@odoo/owl'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const CustomGroupByItem=__exports.CustomGroupByItem=class CustomGroupByItem extends Component{static template="web.CustomGroupByItem";static props={fields:Array,onAddCustomGroup:Function,};get choices(){return this.props.fields.map((f)=>({label:f.string,value:f.name}));}
onSelected(ev){if(ev.target.value){this.props.onAddCustomGroup(ev.target.value);ev.target.value="";}}}
return __exports;});;

/* /web/static/src/search/layout.js */
odoo.define('@web/search/layout',['@odoo/owl','@web/search/control_panel/control_panel','@web/search/search_panel/search_panel'],function(require){'use strict';let __exports={};const{Component,useRef}=require("@odoo/owl");const{ControlPanel}=require("@web/search/control_panel/control_panel");const{SearchPanel}=require("@web/search/search_panel/search_panel");__exports.extractLayoutComponents=extractLayoutComponents;function extractLayoutComponents(params){const layoutComponents={ControlPanel:params.ControlPanel||ControlPanel,SearchPanel:params.SearchPanel||SearchPanel,};return layoutComponents;}
const Layout=__exports.Layout=class Layout extends Component{static template="web.Layout";static props={className:{type:String,optional:true},display:{type:Object,optional:true},slots:{type:Object,optional:true},};static defaultProps={display:{},};setup(){this.components=extractLayoutComponents(this.env.config);this.contentRef=useRef("content");}
get controlPanelSlots(){const slots={...this.props.slots};if(this.env.inDialog){delete slots["layout-buttons"];}
delete slots.default;return slots;}}
return __exports;});;

/* /web/static/src/search/pager_hook.js */
odoo.define('@web/search/pager_hook',['@odoo/owl'],function(require){'use strict';let __exports={};const{useEnv,useSubEnv,useState,onWillRender}=require("@odoo/owl");__exports.usePager=usePager;function usePager(getProps){const env=useEnv();const pagerState=useState({});useSubEnv({config:{...env.config,pagerProps:pagerState,},});onWillRender(()=>{Object.assign(pagerState,getProps()||{total:0});});}
return __exports;});;

/* /web/static/src/search/properties_group_by_item/properties_group_by_item.js */
odoo.define('@web/search/properties_group_by_item/properties_group_by_item',['@web/core/dropdown/accordion_item','@web/core/dropdown/checkbox_item','@web/core/dropdown/dropdown_item','@odoo/owl'],function(require){'use strict';let __exports={};const{AccordionItem,ACCORDION}=require("@web/core/dropdown/accordion_item");const{CheckboxItem}=require("@web/core/dropdown/checkbox_item");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{Component,useState,useChildSubEnv}=require("@odoo/owl");const PropertiesGroupByItem=__exports.PropertiesGroupByItem=class PropertiesGroupByItem extends Component{static template="web.PropertiesGroupByItem";static components={AccordionItem,CheckboxItem,DropdownItem};static props={item:Object,onGroup:Function,};setup(){this.state=useState({groupByItems:[]});useChildSubEnv({[ACCORDION]:{accordionStateChanged:this.beforeOpen.bind(this),},});}
get isActive(){return this.state.groupByItems.some((item)=>item.isActive);}
get isSingleParent(){const uniqueNames=new Set(this.state.groupByItems.map((item)=>item.definitionRecordId));return uniqueNames.size<2;}
async beforeOpen(){if(this.definitionLoaded){return;}
this.definitionLoaded=true;await this.env.searchModel.fillSearchViewItemsProperty();this._updateGroupByItems();}
onGroup(ids){this.props.onGroup(ids);this._updateGroupByItems();}
_updateGroupByItems(){this.state.groupByItems=this.env.searchModel.getSearchItems((searchItem)=>["groupBy","dateGroupBy"].includes(searchItem.type)&&searchItem.isProperty&&searchItem.propertyFieldName===this.props.item.fieldName);}}
return __exports;});;

/* /web/static/src/search/search_arch_parser.js */
odoo.define('@web/search/search_arch_parser',['@web/core/context','@web/core/l10n/translation','@web/core/py_js/py','@web/core/utils/numbers','@web/core/utils/strings','@web/core/utils/xml','@web/search/utils/dates'],function(require){'use strict';let __exports={};const{makeContext}=require("@web/core/context");const{_t}=require("@web/core/l10n/translation");const{evaluateBooleanExpr,evaluateExpr}=require("@web/core/py_js/py");const{clamp}=require("@web/core/utils/numbers");const{exprToBoolean}=require("@web/core/utils/strings");const{visitXML}=require("@web/core/utils/xml");const{DEFAULT_INTERVAL,toGeneratorId}=require("@web/search/utils/dates");const ALL=_t("All");const DEFAULT_LIMIT=200;const DEFAULT_VIEWS_WITH_SEARCH_PANEL=["kanban","list"];function getContextGroubBy(context){try{return makeContext([context]).group_by?.split(":")||[];}catch{return[];}}
function reduceType(type){if(type==="dateFilter"){return"filter";}
if(type==="dateGroupBy"){return"groupBy";}
return type;}
const SearchArchParser=__exports.SearchArchParser=class SearchArchParser{constructor(searchViewDescription,fields,searchDefaults={},searchPanelDefaults={}){const{irFilters,arch}=searchViewDescription;this.fields=fields||{};this.irFilters=irFilters||[];this.arch=arch||"<search/>";this.labels=[];this.preSearchItems=[];this.searchPanelInfo={className:"",fold:false,viewTypes:DEFAULT_VIEWS_WITH_SEARCH_PANEL,};this.sections=[];this.searchDefaults=searchDefaults;this.searchPanelDefaults=searchPanelDefaults;this.currentGroup=[];this.currentTag=null;this.groupNumber=0;this.pregroupOfGroupBys=[];this.optionsParams=null;}
parse(){visitXML(this.arch,(node,visitChildren)=>{switch(node.tagName){case"search":this.visitSearch(node,visitChildren);break;case"searchpanel":return this.visitSearchPanel(node);case"group":this.visitGroup(node,visitChildren);break;case"separator":this.visitSeparator();break;case"field":this.visitField(node);break;case"filter":if(this.optionsParams){this.visitDateOption(node);}else{this.visitFilter(node,visitChildren);}
break;}});return{labels:this.labels,preSearchItems:this.preSearchItems,searchPanelInfo:this.searchPanelInfo,sections:this.sections,};}
pushGroup(tag=null){if(this.currentGroup.length){if(this.currentTag==="groupBy"){this.pregroupOfGroupBys.push(...this.currentGroup);}else{this.preSearchItems.push(this.currentGroup);}}
this.currentTag=tag;this.currentGroup=[];this.groupNumber++;}
visitField(node){this.pushGroup("field");const preField={type:"field"};if(node.hasAttribute("invisible")){preField.invisible=node.getAttribute("invisible");}
if(node.hasAttribute("domain")){preField.domain=node.getAttribute("domain");}
if(node.hasAttribute("filter_domain")){preField.filterDomain=node.getAttribute("filter_domain");}else if(node.hasAttribute("operator")){preField.operator=node.getAttribute("operator");}
if(node.hasAttribute("context")){preField.context=node.getAttribute("context");}
if(node.hasAttribute("name")){const name=node.getAttribute("name");if(!this.fields[name]){throw Error(`Unknown field ${name}`);}
const fieldType=this.fields[name].type;preField.fieldName=name;preField.fieldType=fieldType;if(fieldType!=="properties"&&name in this.searchDefaults){preField.isDefault=true;let value=this.searchDefaults[name];value=Array.isArray(value)?value[0]:value;let operator=preField.operator;if(!operator){let type=fieldType;if(node.hasAttribute("widget")){type=node.getAttribute("widget");}
if(["char","html","many2many","one2many","text"].includes(type)){operator="ilike";}else{operator="=";}}
preField.defaultRank=-10;const{selection,context,relation}=this.fields[name];preField.defaultAutocompleteValue={label:`${value}`,operator,value};if(fieldType==="selection"){const option=selection.find((sel)=>sel[0]===value);if(!option){throw Error();}
preField.defaultAutocompleteValue.label=option[1];}else if(fieldType==="many2one"){this.labels.push((orm)=>{return orm.call(relation,"read",[value,["display_name"]],{context}).then((results)=>{preField.defaultAutocompleteValue.label=results[0]["display_name"];});});}}}else{throw Error();}
if(node.hasAttribute("string")){preField.description=node.getAttribute("string");}else if(preField.fieldName){preField.description=this.fields[preField.fieldName].string;}else{preField.description="Ω";}
this.currentGroup.push(preField);}
visitFilter(node,visitChildren){const preSearchItem={type:"filter"};if(node.hasAttribute("context")){const context=node.getAttribute("context");const[fieldName,defaultInterval]=getContextGroubBy(context);const groupByField=this.fields[fieldName];if(groupByField){preSearchItem.type="groupBy";preSearchItem.fieldName=fieldName;preSearchItem.fieldType=groupByField.type;if(["date","datetime"].includes(groupByField.type)){preSearchItem.type="dateGroupBy";preSearchItem.defaultIntervalId=defaultInterval||DEFAULT_INTERVAL;}}else{preSearchItem.context=context;}}
if(reduceType(preSearchItem.type)!==this.currentTag){this.pushGroup(reduceType(preSearchItem.type));}
if(preSearchItem.type==="filter"){if(node.hasAttribute("date")){const fieldName=node.getAttribute("date");preSearchItem.type="dateFilter";preSearchItem.fieldName=fieldName;preSearchItem.fieldType=this.fields[fieldName].type;const optionsParams={startYear:Number(node.getAttribute("start_year")||-2),endYear:Number(node.getAttribute("end_year")||0),startMonth:Number(node.getAttribute("start_month")||-2),endMonth:Number(node.getAttribute("end_month")||0),customOptions:[],};const defaultOffset=clamp(optionsParams.startMonth,optionsParams.endMonth,0);preSearchItem.defaultGeneratorIds=[toGeneratorId("month",defaultOffset)];if(node.hasAttribute("default_period")){preSearchItem.defaultGeneratorIds=node.getAttribute("default_period").split(",");}
this.optionsParams=optionsParams;visitChildren();preSearchItem.optionsParams=optionsParams;this.optionsParams=null;}
preSearchItem.domain=node.getAttribute("domain")||"[]";}
if(node.hasAttribute("invisible")){preSearchItem.invisible=node.getAttribute("invisible");const fieldName=preSearchItem.fieldName;if(fieldName&&!this.fields[fieldName]){return;}}
preSearchItem.groupNumber=this.groupNumber;if(node.hasAttribute("name")){const name=node.getAttribute("name");preSearchItem.name=name;if(name in this.searchDefaults){preSearchItem.isDefault=true;const value=this.searchDefaults[name];if(["groupBy","dateGroupBy"].includes(preSearchItem.type)){preSearchItem.defaultRank=typeof value==="number"?value:100;}else{preSearchItem.defaultRank=-5;}
if(preSearchItem.type==="dateFilter"&&typeof value==="string"&&!/^(true|1)$/i.test(value)){preSearchItem.defaultGeneratorIds=value.split(",");}}}
if(node.hasAttribute("string")){preSearchItem.description=node.getAttribute("string");}else if(preSearchItem.fieldName){preSearchItem.description=this.fields[preSearchItem.fieldName].string;}else if(node.hasAttribute("help")){preSearchItem.description=node.getAttribute("help");}else if(node.hasAttribute("name")){preSearchItem.description=node.getAttribute("name");}else{preSearchItem.description="Ω";}
this.currentGroup.push(preSearchItem);}
visitDateOption(node){const preDateOption={type:"dateOption"};for(const attribute of["name","string","domain"]){if(!node.getAttribute(attribute)){throw new Error(`Attribute "${attribute}" is missing.`);}}
preDateOption.id=`custom_${node.getAttribute("name")}`;preDateOption.description=node.getAttribute("string");preDateOption.domain=node.getAttribute("domain");this.optionsParams.customOptions.push(preDateOption);}
visitGroup(node,visitChildren){this.pushGroup();visitChildren();this.pushGroup();}
visitSearch(node,visitChildren){visitChildren();this.pushGroup();if(this.pregroupOfGroupBys.length){this.preSearchItems.push(this.pregroupOfGroupBys);}}
visitSearchPanel(searchPanelNode){let hasCategoryWithCounters=false;let hasFilterWithDomain=false;let nextSectionId=1;if(searchPanelNode.hasAttribute("class")){this.searchPanelInfo.className=searchPanelNode.getAttribute("class");}
if(searchPanelNode.hasAttribute("fold")){this.searchPanelInfo.fold=exprToBoolean(searchPanelNode.getAttribute("fold"));}
if(searchPanelNode.hasAttribute("view_types")){this.searchPanelInfo.viewTypes=searchPanelNode.getAttribute("view_types").split(",");}
for(const node of searchPanelNode.children){if(node.nodeType!==1||node.tagName!=="field"){continue;}
if(node.getAttribute("invisible")==="True"||node.getAttribute("invisible")==="1"){continue;}
const attrs={};for(const attrName of node.getAttributeNames()){attrs[attrName]=node.getAttribute(attrName);}
const type=attrs.select==="multi"?"filter":"category";const section={color:attrs.color||null,description:attrs.string||this.fields[attrs.name].string,enableCounters:evaluateBooleanExpr(attrs.enable_counters),expand:evaluateBooleanExpr(attrs.expand),fieldName:attrs.name,icon:attrs.icon||null,id:nextSectionId++,limit:evaluateExpr(attrs.limit||String(DEFAULT_LIMIT)),type,values:new Map(),};if(type==="category"){section.activeValueId=this.searchPanelDefaults[attrs.name];section.icon=section.icon||"fa-folder";section.hierarchize=evaluateBooleanExpr(attrs.hierarchize||"1");section.values.set(false,{childrenIds:[],display_name:ALL.toString(),id:false,bold:true,parentId:false,});hasCategoryWithCounters=hasCategoryWithCounters||section.enableCounters;}else{section.domain=attrs.domain||"[]";section.groupBy=attrs.groupby||null;section.icon=section.icon||"fa-filter";hasFilterWithDomain=hasFilterWithDomain||section.domain!=="[]";}
this.sections.push([section.id,section]);}
if(hasCategoryWithCounters&&hasFilterWithDomain){for(const section of this.sections){if(section.type==="category"){section.enableCounters=false;}}
console.warn("Warning: categories with counters are incompatible with filters having a domain attribute.","All category counters have been disabled to avoid inconsistencies.");}
return false;}
visitSeparator(){this.pushGroup();}}
return __exports;});;

/* /web/static/src/search/search_bar/search_bar.js */
odoo.define('@web/search/search_bar/search_bar',['@web/core/domain','@web/core/l10n/dates','@web/core/registry','@web/core/utils/concurrency','@web/core/utils/hooks','@web/core/domain_selector_dialog/domain_selector_dialog','@web/core/utils/search','@web/core/l10n/translation','@web/search/search_bar_menu/search_bar_menu','@odoo/owl','@web/core/dropdown/dropdown_hooks','@web/core/browser/feature_detection'],function(require){'use strict';let __exports={};const{Domain}=require("@web/core/domain");const{serializeDate,serializeDateTime}=require("@web/core/l10n/dates");const{registry}=require("@web/core/registry");const{KeepLast}=require("@web/core/utils/concurrency");const{useAutofocus,useBus,useService}=require("@web/core/utils/hooks");const{DomainSelectorDialog}=require("@web/core/domain_selector_dialog/domain_selector_dialog");const{fuzzyTest}=require("@web/core/utils/search");const{_t}=require("@web/core/l10n/translation");const{SearchBarMenu}=require("@web/search/search_bar_menu/search_bar_menu");const{Component,useExternalListener,useRef,useState}=require("@odoo/owl");const{useDropdownState}=require("@web/core/dropdown/dropdown_hooks");const{hasTouch}=require("@web/core/browser/feature_detection");const parsers=registry.category("parsers");const CHAR_FIELDS=["char","html","many2many","many2one","one2many","text","properties"];const FOLDABLE_TYPES=["properties","many2one","many2many"];let nextItemId=1;const SUB_ITEMS_DEFAULT_LIMIT=8;const SearchBar=__exports.SearchBar=class SearchBar extends Component{static template="web.SearchBar";static components={SearchBarMenu,};static props={autofocus:{type:Boolean,optional:true},slots:{type:Object,optional:true,shape:{default:{optional:true},"search-bar-additional-menu":{optional:true},},},toggler:{type:Object,optional:true,},};static defaultProps={autofocus:true,};setup(){this.dialogService=useService("dialog");this.fields=this.env.searchModel.searchViewFields;this.searchItemsFields=this.env.searchModel.getSearchItems((f)=>f.type==="field");this.root=useRef("root");this.ui=useService("ui");this.visibilityState=useState(this.props.toggler?.state||{showSearchBar:true});this.state=useState({expanded:[],focusedIndex:0,query:"",subItemsLimits:{},});this.items=useState([]);this.subItems={};this.searchBarDropdownState=useDropdownState();this.orm=useService("orm");this.keepLast=new KeepLast();this.inputRef=this.env.config.disableSearchBarAutofocus||!this.props.autofocus?useRef("autofocus"):useAutofocus({mobile:this.ui.isSmall});useBus(this.env.searchModel,"focus-search",()=>{this.inputRef.el.focus();});useBus(this.env.searchModel,"update",this.render);useExternalListener(window,"click",this.onWindowClick);useExternalListener(window,"keydown",this.onWindowKeydown);}
getSearchItem(id){return this.env.searchModel.searchItems[id];}
async computeState(options={}){const query="query"in options?options.query:this.state.query;const expanded="expanded"in options?options.expanded:this.state.expanded;const focusedIndex="focusedIndex"in options?options.focusedIndex:this.state.focusedIndex;const subItems="subItems"in options?options.subItems:this.subItems;const tasks=[];for(const id of expanded){const searchItem=this.getSearchItem(id);if(searchItem.type==="field"&&searchItem.fieldType==="properties"){tasks.push({id,prom:this.getSearchItemsProperties(searchItem)});}else if(!subItems[id]){if(!this.state.subItemsLimits[id]){this.state.subItemsLimits[id]=SUB_ITEMS_DEFAULT_LIMIT;}
tasks.push({id,prom:this.computeSubItems(searchItem,query)});}}
const prom=this.keepLast.add(Promise.all(tasks.map((task)=>task.prom)));if(tasks.length){const taskResults=await prom;tasks.forEach((task,index)=>{subItems[task.id]=taskResults[index];});}
this.state.expanded=expanded;this.state.query=query;this.state.focusedIndex=focusedIndex;this.subItems=subItems;this.inputRef.el.value=query;const trimmedQuery=this.state.query.trim();this.items.length=0;if(!trimmedQuery){return;}
for(const searchItem of this.searchItemsFields){this.items.push(...this.getItems(searchItem,trimmedQuery));}
this.items.push({title:_t("Add a custom filter"),isAddCustomFilterButton:true,});}
getItems(searchItem,trimmedQuery){const items=[];const isFieldProperty=searchItem.type==="field_property";const fieldType=this.getFieldType(searchItem);let preposition=this.getPreposition(searchItem);if((isFieldProperty&&FOLDABLE_TYPES.includes(fieldType))||fieldType==="properties"){preposition=null;}
if(["selection","boolean","tags"].includes(fieldType)){const booleanOptions=[[true,_t("Yes")],[false,_t("No")],];let options;if(isFieldProperty){const{selection,tags}=searchItem.propertyFieldDefinition||{};options=selection||tags||booleanOptions;}else{options=this.fields[searchItem.fieldName].selection||booleanOptions;}
for(const[value,label]of options){if(fuzzyTest(trimmedQuery.toLowerCase(),label.toLowerCase())){items.push({id:nextItemId++,searchItemDescription:searchItem.description,preposition,searchItemId:searchItem.id,label,operator:searchItem.operator||"=",value,isFieldProperty,});}}
return items;}
const parser=parsers.contains(fieldType)?parsers.get(fieldType):(str)=>str;let value;try{switch(fieldType){case"date":{value=serializeDate(parser(trimmedQuery));break;}
case"datetime":{value=serializeDateTime(parser(trimmedQuery));break;}
case"many2one":{value=trimmedQuery;break;}
default:{value=parser(trimmedQuery);}}}catch{return[];}
const item={id:nextItemId++,searchItemDescription:searchItem.description,preposition,searchItemId:searchItem.id,label:this.state.query,operator:searchItem.operator||(CHAR_FIELDS.includes(fieldType)?"ilike":"="),value,isFieldProperty,};if(isFieldProperty){item.isParent=FOLDABLE_TYPES.includes(fieldType);item.unselectable=FOLDABLE_TYPES.includes(fieldType);item.propertyItemId=searchItem.propertyItemId;}else if(fieldType==="properties"){item.isParent=true;item.unselectable=true;}else if(fieldType==="many2one"){item.isParent=true;}
if(item.isParent){item.isExpanded=this.state.expanded.includes(item.searchItemId);}
items.push(item);if(item.isExpanded){if(searchItem.type==="field"&&searchItem.fieldType==="properties"){for(const subItem of this.subItems[searchItem.id]){items.push(...this.getItems(subItem,trimmedQuery));}}else{items.push(...this.subItems[searchItem.id]);}}
return items;}
getPreposition(searchItem){const fieldType=this.getFieldType(searchItem);return["date","datetime"].includes(fieldType)?_t("at"):_t("for");}
getFieldType(searchItem){const{type}=searchItem.type==="field_property"?searchItem.propertyFieldDefinition:this.fields[searchItem.fieldName];const fieldType=type==="reference"?"char":type;return fieldType;}
getSearchItemsProperties(searchItem){return this.env.searchModel.getSearchItemsProperties(searchItem);}
async computeSubItems(searchItem,query){const field=this.fields[searchItem.fieldName];const context={...this.env.searchModel.domainEvalContext,...field.context};let domain=[];if(searchItem.domain){try{domain=new Domain(searchItem.domain).toList(context);}catch{}}
const relation=searchItem.type==="field_property"?searchItem.propertyFieldDefinition.comodel:field.relation;let nameSearchOperator="ilike";if(query&&query[0]==='"'&&query[query.length-1]==='"'){query=query.slice(1,-1);nameSearchOperator="=";}
const limitToFetch=this.state.subItemsLimits[searchItem.id]+1;const options=await this.orm.call(relation,"name_search",[],{args:domain,operator:nameSearchOperator,context,limit:limitToFetch,name:query.trim(),});let showLoadMore=false;if(options.length===limitToFetch){options.pop();showLoadMore=true;}
const subItems=[];if(options.length){const operator=searchItem.operator||"=";for(const[value,label]of options){subItems.push({id:nextItemId++,isChild:true,searchItemId:searchItem.id,value,label,operator,});}
if(showLoadMore){subItems.push({id:nextItemId++,isChild:true,searchItemId:searchItem.id,label:_t("Load more"),unselectable:true,loadMore:()=>{this.state.subItemsLimits[searchItem.id]+=SUB_ITEMS_DEFAULT_LIMIT;const newSubItems=[...this.subItems];newSubItems[searchItem.id]=undefined;this.computeState({subItems:newSubItems});},});}}else{subItems.push({id:nextItemId++,isChild:true,searchItemId:searchItem.id,label:_t("(no result)"),unselectable:true,});}
return subItems;}
focusFacet(index){const facets=this.root.el.getElementsByClassName("o_searchview_facet");if(facets.length){if(index===undefined){facets[facets.length-1].focus();}else{facets[index].focus();}}}
removeFacet(facet){this.env.searchModel.deactivateGroup(facet.groupId);this.inputRef.el.focus();}
resetState(options={focus:true}){this.state.subItemsLimits={};this.computeState({expanded:[],focusedIndex:0,query:"",subItems:[]});if(options.focus){this.inputRef.el.focus();}}
selectItem(item){if(item.isAddCustomFilterButton){return this.env.searchModel.spawnCustomFilterDialog();}
const searchItem=this.getSearchItem(item.searchItemId);if((searchItem.type==="field"&&searchItem.fieldType==="properties")||(searchItem.type==="field_property"&&item.unselectable)){this.toggleItem(item,!item.isExpanded);return;}
if(!item.unselectable){const{searchItemId,label,operator,value}=item;const autoCompleteValues={label,operator,value};if(value&&value[0]==='"'&&value[value.length-1]==='"'){autoCompleteValues.value=value.slice(1,-1);autoCompleteValues.label=label.slice(1,-1);autoCompleteValues.operator="=";autoCompleteValues.enforceEqual=true;}
this.env.searchModel.addAutoCompletionValues(searchItemId,autoCompleteValues);}
if(item.loadMore){item.loadMore();}else{this.resetState();}}
toggleItem(item,shouldExpand){const id=item.searchItemId;const expanded=[...this.state.expanded];const index=expanded.findIndex((id0)=>id0===id);if(shouldExpand===true){if(index<0){expanded.push(id);}}else{if(index>=0){expanded.splice(index,1);}}
this.computeState({expanded});}
onFacetLabelClick(target,facet){const{domain,groupId}=facet;if(this.env.searchModel.canOrderByCount&&facet.type==="groupBy"){this.env.searchModel.switchGroupBySort();return;}else if(!domain){return;}
const{resModel}=this.env.searchModel;this.dialogService.add(DomainSelectorDialog,{resModel,domain,context:this.env.searchModel.domainEvalContext,onConfirm:(domain)=>this.env.searchModel.splitAndAddDomain(domain,groupId),disableConfirmButton:(domain)=>domain===`[]`,title:_t("Modify Condition"),isDebugMode:this.env.searchModel.isDebugMode,});}
onFacetKeydown(facet,facetIndex,ev){switch(ev.key){case"ArrowLeft":{if(facetIndex===0){this.inputRef.el.focus();}else{this.focusFacet(facetIndex-1);}
break;}
case"ArrowRight":{const facets=this.root.el.getElementsByClassName("o_searchview_facet");if(facetIndex===facets.length-1){this.inputRef.el.focus();}else{this.focusFacet(facetIndex+1);}
break;}
case"Backspace":{this.removeFacet(facet);break;}}}
onFacetRemove(facet){this.removeFacet(facet);}
onItemMousemove(focusedIndex){this.state.focusedIndex=focusedIndex;this.inputRef.el.focus();}
onSearchKeydown(ev){if(ev.isComposing){return;}
const focusedItem=this.items[this.state.focusedIndex];let focusedIndex;switch(ev.key){case"ArrowDown":ev.preventDefault();if(this.items.length){if(this.state.focusedIndex>=this.items.length-1){focusedIndex=0;}else{focusedIndex=this.state.focusedIndex+1;}}else{this.env.searchModel.trigger("focus-view");}
break;case"ArrowUp":ev.preventDefault();if(this.items.length){if(this.state.focusedIndex===0||this.state.focusedIndex>this.items.length-1){focusedIndex=this.items.length-1;}else{focusedIndex=this.state.focusedIndex-1;}}
break;case"ArrowLeft":if(focusedItem&&focusedItem.isParent&&focusedItem.isExpanded){ev.preventDefault();this.toggleItem(focusedItem,false);}else if(focusedItem&&focusedItem.isChild){ev.preventDefault();focusedIndex=this.items.findIndex((item)=>item.isParent&&item.searchItemId===focusedItem.searchItemId);}else if(focusedItem&&focusedItem.isFieldProperty){ev.preventDefault();focusedIndex=this.items.findIndex((item)=>item.isParent&&item.searchItemId===focusedItem.propertyItemId);}else if(ev.target.selectionStart===0){this.focusFacet();}else{}
break;case"ArrowRight":if(ev.target.selectionStart===this.state.query.length){if(focusedItem&&focusedItem.isParent){ev.preventDefault();if(focusedItem.isExpanded){focusedIndex=this.state.focusedIndex+1;}else if(!ev.repeat){this.toggleItem(focusedItem,true);}}else if(ev.target.selectionStart===this.state.query.length){this.focusFacet(0);}}
break;case"Backspace":if(!this.state.query.length){const facets=this.env.searchModel.facets;if(facets.length){this.removeFacet(facets[facets.length-1]);}}
break;case"Enter":if(!this.state.query.length){this.env.searchModel.search();break;}else if(focusedItem){ev.preventDefault();this.selectItem(focusedItem);}
break;case"Tab":if(this.state.query.length&&focusedItem){ev.preventDefault();this.selectItem(focusedItem);}
break;case"Escape":this.resetState();break;}
if(focusedIndex!==undefined){this.state.focusedIndex=focusedIndex;}}
onSearchClick(){if(!hasTouch()&&!this.inputRef.el.value.length){this.searchBarDropdownState.open();}}
onSearchInput(ev){if(!hasTouch()){this.searchBarDropdownState.close();}
const query=ev.target.value;if(query.trim()){this.computeState({query,expanded:[],focusedIndex:0,subItems:[]});}else if(this.items.length){this.resetState();}}
onClickSearchIcon(){const focusedItem=this.items[this.state.focusedIndex];if(!this.state.query.length){this.env.searchModel.search();}else if(focusedItem){this.selectItem(focusedItem);}}
onToggleSearchBar(){this.state.showSearchBar=!this.state.showSearchBar;}
onWindowClick(ev){if(this.items.length&&!this.root.el.contains(ev.target)){this.resetState({focus:false});}}
onWindowKeydown(ev){if(this.items.length&&ev.key==="Escape"){this.resetState();}}}
return __exports;});;

/* /web/static/src/search/search_bar/search_bar_toggler.js */
odoo.define('@web/search/search_bar/search_bar_toggler',['@odoo/owl','@web/core/browser/browser','@web/core/utils/hooks','@web/core/utils/timing'],function(require){'use strict';let __exports={};const{Component,useEffect,useState}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{useService}=require("@web/core/utils/hooks");const{useDebounced}=require("@web/core/utils/timing");const SearchBarToggler=__exports.SearchBarToggler=class SearchBarToggler extends Component{static template="web.SearchBar.Toggler";static props={isSmall:Boolean,showSearchBar:Boolean,toggleSearchBar:Function,};}
__exports.useSearchBarToggler=useSearchBarToggler;function useSearchBarToggler(){const ui=useService("ui");let isToggled=false;const state=useState({isSmall:ui.isSmall,showSearchBar:false,});const updateState=()=>{state.isSmall=ui.isSmall;state.showSearchBar=!ui.isSmall||isToggled;};updateState();function toggleSearchBar(){isToggled=!isToggled;updateState();}
const onResize=useDebounced(updateState,200);useEffect(()=>{browser.addEventListener("resize",onResize);return()=>browser.removeEventListener("resize",onResize);},()=>[]);return{state,component:SearchBarToggler,get props(){return{isSmall:state.isSmall,showSearchBar:state.showSearchBar,toggleSearchBar,};},};}
return __exports;});;

/* /web/static/src/search/search_bar_menu/search_bar_menu.js */
odoo.define('@web/search/search_bar_menu/search_bar_menu',['@odoo/owl','@web/core/confirmation_dialog/confirmation_dialog','@web/core/dropdown/dropdown','@web/search/properties_group_by_item/properties_group_by_item','@web/core/dropdown/dropdown_item','@web/core/registry','@web/core/utils/arrays','@web/core/utils/hooks','@web/core/dropdown/accordion_item','@web/search/custom_group_by_item/custom_group_by_item','@web/core/dropdown/checkbox_item','@web/search/utils/misc','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{ConfirmationDialog}=require("@web/core/confirmation_dialog/confirmation_dialog");const{Dropdown}=require("@web/core/dropdown/dropdown");const{PropertiesGroupByItem}=require("@web/search/properties_group_by_item/properties_group_by_item");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{registry}=require("@web/core/registry");const{sortBy}=require("@web/core/utils/arrays");const{useBus,useService}=require("@web/core/utils/hooks");const{AccordionItem}=require("@web/core/dropdown/accordion_item");const{CustomGroupByItem}=require("@web/search/custom_group_by_item/custom_group_by_item");const{CheckboxItem}=require("@web/core/dropdown/checkbox_item");const{FACET_ICONS,GROUPABLE_TYPES}=require("@web/search/utils/misc");const{_t}=require("@web/core/l10n/translation");const favoriteMenuRegistry=registry.category("favoriteMenu");const SearchBarMenu=__exports.SearchBarMenu=class SearchBarMenu extends Component{static template="web.SearchBarMenu";static components={Dropdown,DropdownItem,CheckboxItem,CustomGroupByItem,AccordionItem,PropertiesGroupByItem,};static props={slots:{type:Object,optional:true,shape:{default:{optional:true},},},dropdownState:{type:Object,optional:true,shape:{isOpen:Boolean,open:Function,close:Function,},},};setup(){this.facet_icons=FACET_ICONS;this.dialogService=useService("dialog");const fields=[];for(const[fieldName,field]of Object.entries(this.env.searchModel.searchViewFields)){if(this.validateField(fieldName,field)){fields.push(Object.assign({name:fieldName},field));}}
this.fields=sortBy(fields,"string");useBus(this.env.searchModel,"update",this.render);}
get filterItems(){return this.env.searchModel.getSearchItems((searchItem)=>["filter","dateFilter"].includes(searchItem.type));}
async onAddCustomFilterClick(){this.env.searchModel.spawnCustomFilterDialog();}
onFilterSelected({itemId,optionId}){if(optionId){this.env.searchModel.toggleDateFilter(itemId,optionId);}else{this.env.searchModel.toggleSearchItem(itemId);}}
get hideCustomGroupBy(){return this.env.searchModel.hideCustomGroupBy||false;}
get groupByItems(){return this.env.searchModel.getSearchItems((searchItem)=>["groupBy","dateGroupBy"].includes(searchItem.type)&&!searchItem.isProperty);}
validateField(fieldName,field){const{groupable,type}=field;return groupable&&fieldName!=="id"&&GROUPABLE_TYPES.includes(type);}
onGroupBySelected({itemId,optionId}){if(optionId){this.env.searchModel.toggleDateGroupBy(itemId,optionId);}else{this.env.searchModel.toggleSearchItem(itemId);}}
onAddCustomGroup(fieldName){this.env.searchModel.createNewGroupBy(fieldName);}
get showComparisonMenu(){return(this.env.searchModel.searchMenuTypes.has("comparison")&&this.env.searchModel.getSearchItems((i)=>i.type==="comparison").length>0);}
get comparisonItems(){return this.env.searchModel.getSearchItems((searchItem)=>searchItem.type==="comparison");}
onComparisonSelected(itemId){this.env.searchModel.toggleSearchItem(itemId);}
get favorites(){return this.env.searchModel.getSearchItems((searchItem)=>searchItem.type==="favorite"&&searchItem.userId!==false);}
get sharedFavorites(){return this.env.searchModel.getSearchItems((searchItem)=>searchItem.type==="favorite"&&searchItem.userId===false);}
get otherItems(){const registryMenus=[];for(const item of favoriteMenuRegistry.getAll()){if("isDisplayed"in item?item.isDisplayed(this.env):true){registryMenus.push({Component:item.Component,groupNumber:item.groupNumber,key:item.Component.name,});}}
return registryMenus;}
onFavoriteSelected(itemId){this.env.searchModel.toggleSearchItem(itemId);}
openConfirmationDialog(itemId,userId){const dialogProps={title:_t("Warning"),body:userId?_t("Are you sure that you want to remove this filter?"):_t("This filter is global and will be removed for everyone."),confirmLabel:_t("Delete Filter"),confirm:()=>this.env.searchModel.deleteFavorite(itemId),cancel:()=>{},};this.dialogService.add(ConfirmationDialog,dialogProps);}}
return __exports;});;

/* /web/static/src/search/search_model.js */
odoo.define('@web/search/search_model',['@web/core/context','@web/core/domain','@web/core/py_js/py','@web/core/user','@web/core/utils/arrays','@web/core/utils/objects','@web/search/search_arch_parser','@web/search/utils/dates','@web/search/utils/misc','@odoo/owl','@web/core/tree_editor/condition_tree','@web/core/l10n/translation','@web/core/tree_editor/utils','@web/core/domain_selector_dialog/domain_selector_dialog','@web/core/domain_selector/utils'],function(require){'use strict';let __exports={};const{makeContext}=require("@web/core/context");const{Domain}=require("@web/core/domain");const{evaluateExpr}=require("@web/core/py_js/py");const{user}=require("@web/core/user");const{sortBy,groupBy}=require("@web/core/utils/arrays");const{deepCopy}=require("@web/core/utils/objects");const{SearchArchParser}=require("@web/search/search_arch_parser");const{constructDateDomain,DEFAULT_INTERVAL,getComparisonOptions,getIntervalOptions,getPeriodOptions,rankInterval,yearSelected,}=require("@web/search/utils/dates");const{FACET_ICONS,FACET_COLORS}=require("@web/search/utils/misc");const{EventBus,toRaw}=require("@odoo/owl");const{domainFromTree,treeFromDomain}=require("@web/core/tree_editor/condition_tree");const{_t}=require("@web/core/l10n/translation");const{useGetTreeDescription,useMakeGetFieldDef}=require("@web/core/tree_editor/utils");const{DomainSelectorDialog}=require("@web/core/domain_selector_dialog/domain_selector_dialog");const{getDefaultDomain}=require("@web/core/domain_selector/utils");const{DateTime}=luxon;function hasValues(section){const{errorMsg,type,values}=section;if(errorMsg){return true;}
switch(type){case"category":{return values&&values.size>1;}
case"filter":{return values&&values.size>0;}}}
function mapToArray(map){const result=[];for(const[key,val]of map){const valCopy=Object.assign({},val);result.push([key,valCopy]);}
return result;}
function arraytoMap(array){return new Map(array);}
function execute(op,source,target){const{query,nextId,nextGroupId,nextGroupNumber,searchItems,searchPanelInfo,sections}=source;target.nextGroupId=nextGroupId;target.nextGroupNumber=nextGroupNumber;target.nextId=nextId;target.query=query;target.searchItems=searchItems;target.searchPanelInfo=searchPanelInfo;target.sections=op(sections);for(const[,section]of target.sections){section.values=op(section.values);if(section.groups){section.groups=op(section.groups);for(const[,group]of section.groups){group.values=op(group.values);}}}}
const FAVORITE_PRIVATE_GROUP=1;const FAVORITE_SHARED_GROUP=2;const SearchModel=__exports.SearchModel=class SearchModel extends EventBus{constructor(env,services,args){super();this.env=env;this.setup(services,args);}
setup(services){const{field:fieldService,name:nameService,orm,view,dialog}=services;this.orm=orm;this.fieldService=fieldService;this.viewService=view;this.dialog=dialog;this.orderByCount=false;this.getDomainTreeDescription=useGetTreeDescription(fieldService,nameService);this.makeGetFieldDef=useMakeGetFieldDef(fieldService);this.referenceMoment=DateTime.local();this.comparisonOptions=getComparisonOptions();this.intervalOptions=getIntervalOptions();}
async load(config){const{resModel}=config;if(!resModel){throw Error(`SearchPanel config should have a "resModel" key`);}
this.resModel=resModel;this._reset();const{comparison,context,domain,groupBy,hideCustomGroupBy,orderBy}=config;this.globalComparison=comparison;this.globalContext=toRaw(Object.assign({},context));this.globalDomain=domain||[];this.globalGroupBy=groupBy||[];this.globalOrderBy=orderBy||[];this.hideCustomGroupBy=hideCustomGroupBy;this.searchMenuTypes=new Set(config.searchMenuTypes||["filter","groupBy","favorite"]);this.canOrderByCount=config.canOrderByCount;let{irFilters,loadIrFilters,searchViewArch,searchViewFields,searchViewId}=config;const loadSearchView=searchViewId!==undefined&&(!searchViewArch||!searchViewFields||(!irFilters&&loadIrFilters));const searchViewDescription={};if(loadSearchView){const result=await this.viewService.loadViews({context:this.globalContext,resModel,views:[[searchViewId,"search"]],},{actionId:this.env.config.actionId,embeddedActionId:this.env.config.currentEmbeddedActionId,loadIrFilters:loadIrFilters||false,});Object.assign(searchViewDescription,result.views.search);searchViewFields=searchViewFields||result.fields;}
if(searchViewArch){searchViewDescription.arch=searchViewArch;}
if(irFilters){searchViewDescription.irFilters=irFilters;}
if(searchViewId!==undefined){searchViewDescription.viewId=searchViewId;}
this.searchViewArch=searchViewDescription.arch||"<search/>";this.searchViewFields=searchViewFields||{};if(searchViewDescription.irFilters){this.irFilters=searchViewDescription.irFilters;}
if(searchViewDescription.viewId!==undefined){this.searchViewId=searchViewDescription.viewId;}
const{searchDefaults,searchPanelDefaults}=this._extractSearchDefaultsFromGlobalContext();if(config.state){this._importState(config.state);this.__legacyParseSearchPanelArchAnyway(searchViewDescription,searchViewFields);this.display=this._getDisplay(config.display);if(!this.searchPanelInfo.loaded){return this._reloadSections();}
return;}
this.blockNotification=true;this.searchItems={};this.query=[];this.nextId=1;this.nextGroupId=1;this.nextGroupNumber=1;const parser=new SearchArchParser(searchViewDescription,searchViewFields,searchDefaults,searchPanelDefaults);const{labels,preSearchItems,searchPanelInfo,sections}=parser.parse();this.searchPanelInfo={...searchPanelInfo,loaded:false,shouldReload:false};await Promise.all(labels.map((cb)=>cb(this.orm)));for(const preGroup of preSearchItems||[]){this._createGroupOfSearchItems(preGroup);}
this.nextGroupNumber=1+Math.max(...Object.values(this.searchItems).map((i)=>i.groupNumber||0),0);const dateFilters=Object.values(this.searchItems).filter((searchElement)=>searchElement.type==="dateFilter");if(dateFilters.length){this._createGroupOfComparisons(dateFilters);}
const{dynamicFilters}=config;if(dynamicFilters){this._createGroupOfDynamicFilters(dynamicFilters);}
const defaultFavoriteId=this._createGroupOfFavorites(this.irFilters||[]);const activateFavorite="activateFavorite"in config?config.activateFavorite:true;this._activateDefaultSearchItems(activateFavorite?defaultFavoriteId:null);this.sections=new Map(sections||[]);this.display=this._getDisplay(config.display);if(this.display.searchPanel){this.searchDomain=this._getDomain({withSearchPanel:false});this.sectionsPromise=this._fetchSections(this.categories,this.filters).then(()=>{for(const{fieldName,values}of this.filters){const filterDefaults=searchPanelDefaults[fieldName]||[];for(const valueId of filterDefaults){const value=values.get(valueId);if(value){value.checked=true;}}}});if(Object.keys(searchPanelDefaults).length||this._shouldWaitForData(false)){await this.sectionsPromise;}}
this.blockNotification=false;}
async reload(config={}){this._reset();const{comparison,context,domain,groupBy,orderBy}=config;this.globalContext=Object.assign({},context);this.globalDomain=domain||[];this.globalComparison=comparison;this.globalGroupBy=groupBy||[];this.globalOrderBy=orderBy||[];this._extractSearchDefaultsFromGlobalContext();await this._reloadSections();}
get categories(){return[...this.sections.values()].filter((s)=>s.type==="category");}
get context(){if(!this._context){this._context=makeContext([this.globalContext,this._getContext()]);}
return deepCopy(this._context);}
get domain(){if(!this._domain){this._domain=this._getDomain();}
return deepCopy(this._domain);}
get domainString(){return this._getDomain({raw:true}).toString();}
get domainEvalContext(){return Object.assign({},this.globalContext,user.context);}
get comparison(){if(!this.searchMenuTypes.has("comparison")){return null;}
if(this._comparison===undefined){if(this.globalComparison){this._comparison=this.globalComparison;}else{const comparison=this.getFullComparison();if(comparison){const{fieldName,range,rangeDescription,comparisonRange,comparisonRangeDescription,}=comparison;const domains=[{arrayRepr:Domain.and([this.domain,range]).toList(),description:rangeDescription,},{arrayRepr:Domain.and([this.domain,comparisonRange]).toList(),description:comparisonRangeDescription,},];this._comparison={domains,fieldName};}else{this._comparison=null;}}}
return deepCopy(this._comparison);}
get facets(){const isValidType=(type)=>!["groupBy","comparison"].includes(type)||this.searchMenuTypes.has(type);const facets=[];for(const facet of this._getFacets()){if(!isValidType(facet.type)){continue;}
facets.push(facet);}
return facets;}
get filters(){return[...this.sections.values()].filter((s)=>s.type==="filter");}
get groupBy(){if(!this.searchMenuTypes.has("groupBy")){return[];}
if(!this._groupBy){this._groupBy=this._getGroupBy();}
return deepCopy(this._groupBy);}
get orderBy(){if(!this._orderBy){this._orderBy=this._getOrderBy();}
return deepCopy(this._orderBy);}
get isDebugMode(){return!!this.env.debug;}
addAutoCompletionValues(searchItemId,autocompleteValue){const searchItem=this.searchItems[searchItemId];if(!["field","field_property"].includes(searchItem.type)){return;}
const{label,value,operator}=autocompleteValue;const queryElem=this.query.find((queryElem)=>queryElem.searchItemId===searchItemId&&"autocompleteValue"in queryElem&&queryElem.autocompleteValue.value===value&&queryElem.autocompleteValue.operator===operator);if(!queryElem){this.query.push({searchItemId,autocompleteValue});}else{queryElem.autocompleteValue.label=label;}
this._notify();}
clearQuery(){this.query=[];this.orderByCount=false;this._notify();}
async createNewFavorite(params){const{preFavorite,irFilter}=this._getIrFilterDescription(params);const serverSideId=await this._createIrFilters(irFilter);this.blockNotification=true;this.clearQuery();const favorite={...preFavorite,type:"favorite",id:this.nextId,groupId:this.nextGroupId,groupNumber:preFavorite.userId?FAVORITE_PRIVATE_GROUP:FAVORITE_SHARED_GROUP,removable:true,serverSideId,};this.searchItems[this.nextId]=favorite;this.query.push({searchItemId:this.nextId});this.nextGroupId++;this.nextId++;this.blockNotification=false;this._notify();}
async _createIrFilters(irFilter){const serverSideId=await this.orm.call("ir.filters","create_or_replace",[irFilter]);this.env.bus.trigger("CLEAR-CACHES");return serverSideId;}
createNewFilters(prefilters){if(!prefilters.length){return[];}
prefilters.forEach((preFilter)=>{const filter=Object.assign(preFilter,{groupId:this.nextGroupId,groupNumber:this.nextGroupNumber,id:this.nextId,type:"filter",});this.searchItems[this.nextId]=filter;this.query.push({searchItemId:this.nextId});this.nextId++;});this.nextGroupId++;this.nextGroupNumber++;this._notify();}
createNewGroupBy(fieldName,{interval,invisible}={}){const field=this.searchViewFields[fieldName];const{string,type:fieldType}=field;const firstGroupBy=Object.values(this.searchItems).find((f)=>f.type==="groupBy");const preSearchItem={description:string||fieldName,fieldName,fieldType,groupId:firstGroupBy?firstGroupBy.groupId:this.nextGroupId++,groupNumber:this.nextGroupNumber,id:this.nextId,custom:true,};if(invisible){preSearchItem.invisible="True";}
if(["date","datetime"].includes(fieldType)){this.searchItems[this.nextId]=Object.assign({type:"dateGroupBy",defaultIntervalId:interval||DEFAULT_INTERVAL},preSearchItem);this.toggleDateGroupBy(this.nextId);}else{this.searchItems[this.nextId]=Object.assign({type:"groupBy"},preSearchItem);this.toggleSearchItem(this.nextId);}
this.nextGroupNumber++;this.nextId++;this._notify();}
deactivateGroup(groupId){this.query=this.query.filter((queryElem)=>{const searchItem=this.searchItems[queryElem.searchItemId];return searchItem.groupId!==groupId;});this._checkComparisonStatus();this._checkOrderByCountStatus();this._notify();}
async deleteFavorite(favoriteId){const searchItem=this.searchItems[favoriteId];if(searchItem.type!=="favorite"){return;}
await this._deleteIrFilters(searchItem);const index=this.query.findIndex((queryElem)=>queryElem.searchItemId===favoriteId);delete this.searchItems[favoriteId];if(index>=0){this.query.splice(index,1);}
this._notify();}
async _deleteIrFilters(searchItem){const{serverSideId}=searchItem;await this.orm.unlink("ir.filters",[serverSideId]);this.env.bus.trigger("CLEAR-CACHES");}
exportState(){const state={};execute(mapToArray,this,state);return state;}
getFullComparison(){let searchItem=null;for(const queryElem of this.query.slice().reverse()){const item=this.searchItems[queryElem.searchItemId];if(item.type==="comparison"){searchItem=item;break;}else if(item.type==="favorite"&&item.comparison){searchItem=item;break;}}
if(!searchItem){return null;}else if(searchItem.type==="favorite"){return searchItem.comparison;}
const{dateFilterId,comparisonOptionId}=searchItem;const dateFilter=this.searchItems[dateFilterId];const{fieldName,description:dateFilterDescription}=dateFilter;const selectedGeneratorIds=this._getSelectedGeneratorIds(dateFilterId);const{domain:range,description:rangeDescription}=constructDateDomain(this.referenceMoment,dateFilter,selectedGeneratorIds);const{domain:comparisonRange,description:comparisonRangeDescription}=constructDateDomain(this.referenceMoment,dateFilter,selectedGeneratorIds,comparisonOptionId);return{comparisonId:comparisonOptionId,fieldName,fieldDescription:dateFilterDescription,range:range.toList(),rangeDescription,comparisonRange:comparisonRange.toList(),comparisonRangeDescription,};}
getIrFilterValues(params){const{irFilter}=this._getIrFilterDescription(params);return irFilter;}
getPreFavoriteValues(params){const{preFavorite}=this._getIrFilterDescription(params);return preFavorite;}
getSearchItems(predicate){const searchItems=[];for(const searchItem of Object.values(this.searchItems)){const enrichedSearchitem=this._enrichItem(searchItem);if(enrichedSearchitem){const isInvisible="invisible"in searchItem&&evaluateExpr(searchItem.invisible,this.globalContext);if(!isInvisible&&(!predicate||predicate(enrichedSearchitem))){searchItems.push(enrichedSearchitem);}}}
if(searchItems.some((f)=>f.type==="favorite")){searchItems.sort((f1,f2)=>f1.groupNumber-f2.groupNumber);}
return searchItems;}
getSections(predicate){let sections=[...this.sections.values()].map((section)=>Object.assign({},section,{empty:!hasValues(section)}));if(predicate){sections=sections.filter(predicate);}
return sections.sort((s1,s2)=>s1.index-s2.index);}
search(){this.trigger("update");}
async splitAndAddDomain(domain,groupId){const group=groupId?this._getGroups().find((g)=>g.id===groupId):null;let context;if(group){const contexts=[];for(const activeItem of group.activeItems){const context=this._getSearchItemContext(activeItem);if(context){contexts.push(context);}}
context=makeContext(contexts);}
const getFieldDef=await this.makeGetFieldDef(this.resModel,treeFromDomain(domain));const tree=treeFromDomain(domain,{distributeNot:!this.isDebugMode,getFieldDef});const containsChildren=!tree.negate&&tree.type==="connector"&&tree.value=="&";const trees=containsChildren?tree.children:[tree];const promises=trees.map(async(tree)=>{const description=await this.getDomainTreeDescription(this.resModel,tree);const preFilter={description,domain:domainFromTree(tree),invisible:"True",type:"filter",};if(context){preFilter.context=context;}
return preFilter;});const preFilters=await Promise.all(promises);this.blockNotification=true;if(group){const firstActiveItem=group.activeItems[0];const firstSearchItem=this.searchItems[firstActiveItem.searchItemId];const{type}=firstSearchItem;if(type==="favorite"){const activeItemGroupBys=this._getSearchItemGroupBys(firstActiveItem);for(const activeItemGroupBy of activeItemGroupBys){const[fieldName,interval]=activeItemGroupBy.split(":");this.createNewGroupBy(fieldName,{interval,invisible:true});}
const index=this.query.length-activeItemGroupBys.length;this.query=[...this.query.slice(index),...this.query.slice(0,index)];}
this.deactivateGroup(groupId);}
for(const preFilter of preFilters){this.createNewFilters([preFilter]);}
this.blockNotification=false;this._notify();}
toggleCategoryValue(sectionId,valueId){const category=this.sections.get(sectionId);category.activeValueId=valueId;this._notify();}
toggleFilterValues(sectionId,valueIds,forceTo=null){const filter=this.sections.get(sectionId);for(const valueId of valueIds){const value=filter.values.get(valueId);value.checked=forceTo===null?!value.checked:forceTo;}
this._notify();}
clearSections(sectionIds){for(const sectionId of sectionIds){const section=this.sections.get(sectionId);if(section.type==="category"){section.activeValueId=false;}else{for(const[,value]of section.values){value.checked=false;}}}
this._notify();}
toggleSearchItem(searchItemId){const searchItem=this.searchItems[searchItemId];switch(searchItem.type){case"dateFilter":case"dateGroupBy":case"field_property":case"field":{return;}}
const index=this.query.findIndex((queryElem)=>queryElem.searchItemId===searchItemId);if(index>=0){this.query.splice(index,1);this._checkOrderByCountStatus();}else{if(searchItem.type==="favorite"){this.query=[];}else if(searchItem.type==="comparison"){this.query=this.query.filter((queryElem)=>{const{type}=this.searchItems[queryElem.searchItemId];return type!=="comparison";});}
this.query.push({searchItemId});}
this._notify();}
toggleDateFilter(searchItemId,generatorId){const searchItem=this.searchItems[searchItemId];if(searchItem.type!=="dateFilter"){return;}
const generatorIds=generatorId?[generatorId]:searchItem.defaultGeneratorIds;for(const generatorId of generatorIds){const index=this.query.findIndex((queryElem)=>queryElem.searchItemId===searchItemId&&"generatorId"in queryElem&&queryElem.generatorId===generatorId);if(index>=0){this.query.splice(index,1);if(!yearSelected(this._getSelectedGeneratorIds(searchItemId))){this.query=this.query.filter((queryElem)=>queryElem.searchItemId!==searchItemId);}}else{if(generatorId.startsWith("custom")){const comparisonId=this._getActiveComparison()?.id;this.query=this.query.filter((queryElem)=>![searchItemId,comparisonId].includes(queryElem.searchItemId));this.query.push({searchItemId,generatorId});continue;}
this.query=this.query.filter((queryElem)=>queryElem.searchItemId!==searchItemId||!queryElem.generatorId.startsWith("custom"));this.query.push({searchItemId,generatorId});if(!yearSelected(this._getSelectedGeneratorIds(searchItemId))){const{defaultYearId}=getPeriodOptions(this.referenceMoment,searchItem.optionsParams).find((o)=>o.id===generatorId);this.query.push({searchItemId,generatorId:defaultYearId});}}}
this._checkComparisonStatus();this._notify();}
toggleDateGroupBy(searchItemId,intervalId){const searchItem=this.searchItems[searchItemId];if(searchItem.type!=="dateGroupBy"){return;}
intervalId=intervalId||searchItem.defaultIntervalId;const index=this.query.findIndex((queryElem)=>queryElem.searchItemId===searchItemId&&"intervalId"in queryElem&&queryElem.intervalId===intervalId);if(index>=0){this.query.splice(index,1);this._checkOrderByCountStatus();}else{this.query.push({searchItemId,intervalId});}
this._notify();}
async spawnCustomFilterDialog(){const domain=getDefaultDomain(this.searchViewFields);this.dialog.add(DomainSelectorDialog,{resModel:this.resModel,defaultConnector:"|",domain,context:this.domainEvalContext,onConfirm:(domain)=>this.splitAndAddDomain(domain),disableConfirmButton:(domain)=>domain===`[]`,title:_t("Add Custom Filter"),confirmButtonText:_t("Add"),discardButtonText:_t("Cancel"),isDebugMode:this.isDebugMode,});}
switchGroupBySort(){if(this.orderByCount==="Desc"){this.orderByCount="Asc";}else{this.orderByCount="Desc";}
this._notify();}
async getSearchItemsProperties(searchItem){if(searchItem.type!=="field"||searchItem.fieldType!=="properties"){return[];}
const field=this.searchViewFields[searchItem.fieldName];const definitionRecord=field.definition_record;const result=await this._fetchPropertiesDefinition(this.resModel,searchItem.fieldName);const searchItemIds=new Set();const existingFieldProperties={};for(const item of Object.values(this.searchItems)){if(item.type==="field_property"&&item.propertyItemId===searchItem.id){existingFieldProperties[item.propertyFieldDefinition.name]=item;}}
for(const{definitionRecordId,definitionRecordName,definitions}of result){for(const definition of definitions){if(definition.type==="separator"){continue;}
const existingSearchItem=existingFieldProperties[definition.name];if(existingSearchItem){existingSearchItem.description=`${definition.string} (${definitionRecordName})`;searchItemIds.add(existingSearchItem.id);continue;}
const id=this.nextId++;const newSearchItem={id,type:"field_property",fieldName:searchItem.fieldName,propertyDomain:[definitionRecord,"=",definitionRecordId],propertyFieldDefinition:definition,propertyItemId:searchItem.id,description:`${definition.string} (${definitionRecordName})`,groupId:this.nextGroupId++,};if(["many2many","tags"].includes(definition.type)){newSearchItem.operator="in";}
this.searchItems[id]=newSearchItem;searchItemIds.add(id);}}
return this.getSearchItems((searchItem)=>searchItemIds.has(searchItem.id));}
async fillSearchViewItemsProperty(){if(!this.searchViewFields){return;}
const fields=Object.values(this.searchViewFields);for(const field of fields){if(field.type!=="properties"){continue;}
const result=await this._fetchPropertiesDefinition(this.resModel,field.name);const searchItemsNames=Object.values(this.searchItems).filter((item)=>item.isProperty&&["groupBy","dateGroupBy"].includes(item.type)).map((item)=>item.fieldName);for(const{definitionRecordId,definitionRecordName,definitions}of result){const groupNames=definitions.map((definition)=>`group_by_${field.name}.${definition.name}`);Object.values(this.searchItems).forEach((searchItem)=>{if(searchItem.isProperty&&searchItem.definitionRecordId===definitionRecordId&&["groupBy","dateGroupBy"].includes(searchItem.type)&&!groupNames.includes(searchItem.name)){searchItem.type="group_by_property_deleted";}});for(const definition of definitions){const fullName=`${field.name}.${definition.name}`;this.searchViewFields[fullName]={name:fullName,readonly:false,relation:definition.comodel,required:false,searchable:false,selection:definition.selection,sortable:true,store:true,string:definition.string,type:definition.type,relatedPropertyField:field,};if(!searchItemsNames.includes(fullName)&&definition.type!=="separator"){const groupByItem={description:definition.string,definitionRecordId,definitionRecordName,fieldName:fullName,fieldType:definition.type,isProperty:true,name:`group_by_${field.name}.${definition.name}`,propertyFieldName:field.name,type:["datetime","date"].includes(definition.type)?"dateGroupBy":"groupBy",};this._createGroupOfSearchItems([groupByItem]);}}}}}
async _fetchPropertiesDefinition(resModel,fieldName){const domain=[];if(this.context.active_id){domain.push(["id","=",this.context.active_id]);}
const definitions=await this.fieldService.loadPropertyDefinitions(resModel,fieldName,domain);const result=groupBy(Object.values(definitions),(definition)=>definition.record_id);return Object.entries(result).map(([recordId,definitions])=>{return{definitionRecordId:parseInt(recordId),definitionRecordName:definitions[0]?.record_name,definitions,};});}
_activateDefaultSearchItems(defaultFavoriteId){if(defaultFavoriteId){this.toggleSearchItem(defaultFavoriteId);}else{Object.values(this.searchItems).filter((f)=>f.isDefault&&f.type!=="favorite").sort((f1,f2)=>(f1.defaultRank||100)-(f2.defaultRank||100)).forEach((f)=>{if(f.type==="dateFilter"){this.toggleDateFilter(f.id);}else if(f.type==="dateGroupBy"){this.toggleDateGroupBy(f.id);}else if(f.type==="field"){this.addAutoCompletionValues(f.id,f.defaultAutocompleteValue);}else{this.toggleSearchItem(f.id);}});}}
_checkComparisonStatus(){const activeComparison=this._getActiveComparison();if(!activeComparison){return;}
const{dateFilterId,id}=activeComparison;const dateFilterIsActive=this.query.some((queryElem)=>queryElem.searchItemId===dateFilterId);if(!dateFilterIsActive){this.query=this.query.filter((queryElem)=>queryElem.searchItemId!==id);}}
_checkOrderByCountStatus(){if(this.orderByCount&&!this.query.some((item)=>["dateGroupBy","groupBy"].includes(this.searchItems[item.searchItemId].type))){this.orderByCount=false;}}
_createCategoryTree(sectionId,result){const category=this.sections.get(sectionId);delete category.errorMsg;let{error_msg,parent_field:parentField,values}=result;if(error_msg){category.errorMsg=error_msg;values=[];}
if(category.hierarchize){category.parentField=parentField;}
for(const value of values){category.values.set(value.id,Object.assign({},value,{childrenIds:[],parentId:value[parentField]||false,}));}
for(const value of values){const{parentId}=category.values.get(value.id);if(parentId&&category.values.has(parentId)){category.values.get(parentId).childrenIds.push(value.id);}}
category.rootIds=[false];for(const value of values){const{parentId}=category.values.get(value.id);if(!parentId){category.rootIds.push(value.id);}}
const valueIds=[false,...values.map((val)=>val.id)];this._ensureCategoryValue(category,valueIds);}
_createFilterTree(sectionId,result){const filter=this.sections.get(sectionId);delete filter.errorMsg;let{error_msg,values}=result;if(error_msg){filter.errorMsg=error_msg;values=[];}
values.forEach((value)=>{const oldValue=filter.values.get(value.id);value.checked=oldValue?oldValue.checked:false;});filter.values=new Map();const groupIds=[];if(filter.groupBy){const groups=new Map();for(const value of values){const groupId=value.group_id;if(!groups.has(groupId)){if(groupId){groupIds.push(groupId);}
groups.set(groupId,{id:groupId,name:value.group_name,values:new Map(),tooltip:value.group_tooltip,sequence:value.group_sequence,color_index:value.color_index,});const oldGroup=filter.groups&&filter.groups.get(groupId);groups.get(groupId).state=(oldGroup&&oldGroup.state)||false;}
groups.get(groupId).values.set(value.id,value);}
filter.groups=groups;filter.sortedGroupIds=sortBy(groupIds,(id)=>groups.get(id).sequence||groups.get(id).name);for(const group of filter.groups.values()){for(const[valueId,value]of group.values){filter.values.set(valueId,value);}}}else{for(const value of values){filter.values.set(value.id,value);}}}
_createGroupOfComparisons(dateFilters){const preSearchItem=[];for(const dateFilter of dateFilters){for(const comparisonOption of this.comparisonOptions){const{id:dateFilterId,description}=dateFilter;const preFilter={type:"comparison",comparisonOptionId:comparisonOption.id,description:`${description}: ${comparisonOption.description}`,dateFilterId,};preSearchItem.push(preFilter);}}
this._createGroupOfSearchItems(preSearchItem);}
_createGroupOfDynamicFilters(dynamicFilters){const pregroup=dynamicFilters.map((filter)=>{return{groupNumber:this.nextGroupNumber,description:filter.description,domain:filter.domain,isDefault:"is_default"in filter?filter.is_default:true,type:"filter",};});this.nextGroupNumber++;this._createGroupOfSearchItems(pregroup);}
_createGroupOfFavorites(irFilters){let defaultFavoriteId=null;irFilters.forEach((irFilter)=>{const favorite=this._irFilterToFavorite(irFilter);this._createGroupOfSearchItems([favorite]);if(favorite.isDefault){defaultFavoriteId=favorite.id;}});return defaultFavoriteId;}
_createGroupOfSearchItems(pregroup){pregroup.forEach((preSearchItem)=>{const searchItem=Object.assign(preSearchItem,{groupId:this.nextGroupId,id:this.nextId,});this.searchItems[this.nextId]=searchItem;this.nextId++;});this.nextGroupId++;}
_enrichItem(searchItem){if(searchItem.type==="field"&&searchItem.fieldType==="properties"){return{...searchItem};}
const queryElements=this.query.filter((queryElem)=>queryElem.searchItemId===searchItem.id);const isActive=Boolean(queryElements.length);const enrichSearchItem=Object.assign({isActive},searchItem);function _enrichOptions(options,selectedIds){return options.map((o)=>{const{description,id,groupNumber}=o;const isActive=selectedIds.some((optionId)=>optionId===id);return{description,id,groupNumber,isActive};});}
switch(searchItem.type){case"comparison":{const{dateFilterId}=searchItem;const dateFilterIsActive=this.query.some((queryElem)=>queryElem.searchItemId===dateFilterId&&!queryElem.generatorId.startsWith("custom"));if(!dateFilterIsActive){return null;}
break;}
case"dateFilter":enrichSearchItem.options=_enrichOptions(getPeriodOptions(this.referenceMoment,searchItem.optionsParams),queryElements.map((queryElem)=>queryElem.generatorId));break;case"dateGroupBy":enrichSearchItem.options=_enrichOptions(this.intervalOptions,queryElements.map((queryElem)=>queryElem.intervalId));break;case"field":case"field_property":enrichSearchItem.autocompleteValues=queryElements.map((queryElem)=>queryElem.autocompleteValue);break;}
return enrichSearchItem;}
_ensureCategoryValue(category,valueIds){if(!valueIds.includes(category.activeValueId)){category.activeValueId=valueIds[0];}}
_extractSearchDefaultsFromGlobalContext(){const searchDefaults={};const searchPanelDefaults={};for(const key in this.globalContext){const defaultValue=this.globalContext[key];const searchDefaultMatch=/^search_default_(.*)$/.exec(key);if(searchDefaultMatch){if(defaultValue){searchDefaults[searchDefaultMatch[1]]=defaultValue;}
delete this.globalContext[key];continue;}
const searchPanelDefaultMatch=/^searchpanel_default_(.*)$/.exec(key);if(searchPanelDefaultMatch){searchPanelDefaults[searchPanelDefaultMatch[1]]=defaultValue;delete this.globalContext[key];}}
return{searchDefaults,searchPanelDefaults};}
async _fetchCategories(categories){const filterDomain=this._getFilterDomain();const searchDomain=this.searchDomain;await Promise.all(categories.map(async(category)=>{const result=await this.orm.call(this.resModel,"search_panel_select_range",[category.fieldName],{category_domain:this._getCategoryDomain(category.id),context:this.globalContext,enable_counters:category.enableCounters,expand:category.expand,filter_domain:filterDomain,hierarchize:category.hierarchize,limit:category.limit,search_domain:searchDomain,});this._createCategoryTree(category.id,result);}));}
async _fetchFilters(filters){const evalContext={};for(const category of this.categories){evalContext[category.fieldName]=category.activeValueId;}
const categoryDomain=this._getCategoryDomain();const searchDomain=this.searchDomain;await Promise.all(filters.map(async(filter)=>{const result=await this.orm.call(this.resModel,"search_panel_select_multi_range",[filter.fieldName],{category_domain:categoryDomain,comodel_domain:new Domain(filter.domain).toList(evalContext),context:this.globalContext,enable_counters:filter.enableCounters,filter_domain:this._getFilterDomain(filter.id),expand:filter.expand,group_by:filter.groupBy||false,group_domain:this._getGroupDomain(filter),limit:filter.limit,search_domain:searchDomain,});this._createFilterTree(filter.id,result);}));}
async _fetchSections(categoriesToLoad,filtersToLoad){await this._fetchCategories(categoriesToLoad);await this._fetchFilters(filtersToLoad);this.searchPanelInfo.loaded=true;}
_getActiveComparison(){for(const queryElem of this.query){const searchItem=this.searchItems[queryElem.searchItemId];if(searchItem.type==="comparison"){return searchItem;}}
return null;}
_getCategoryDomain(excludedCategoryId){const domain=[];for(const category of this.categories){if(category.id===excludedCategoryId||!category.activeValueId){continue;}
const field=this.searchViewFields[category.fieldName];const operator=field.type==="many2one"&&category.parentField?"child_of":"=";domain.push([category.fieldName,operator,category.activeValueId]);}
return domain;}
_getContext(){const groups=this._getGroups();const contexts=[user.context];for(const group of groups){for(const activeItem of group.activeItems){const context=this._getSearchItemContext(activeItem);if(context){contexts.push(context);}}}
let context;try{context=makeContext(contexts);return context;}catch(error){throw new Error(_t("Failed to evaluate the context: %(context)s.\n%(error)s",{context,error:error.message,}));}}
_getDateFilterDomain(dateFilter,generatorIds,key="domain"){const dateFilterRange=constructDateDomain(this.referenceMoment,dateFilter,generatorIds);return dateFilterRange[key];}
_getDisplay(display={}){const{viewTypes}=this.searchPanelInfo;const{bannerRoute,viewType}=this.env.config;return{controlPanel:"controlPanel"in display?display.controlPanel:{},searchPanel:this.sections.size&&(!viewType||viewTypes.includes(viewType))&&("searchPanel"in display?display.searchPanel:true),banner:Boolean(bannerRoute),};}
_getDomain(params={}){const withSearchPanel="withSearchPanel"in params?params.withSearchPanel:true;const withGlobal="withGlobal"in params?params.withGlobal:true;const groups=this._getGroups();const domains=[];if(withGlobal){domains.push(this.globalDomain);}
for(const group of groups){const groupActiveItemDomains=[];for(const activeItem of group.activeItems){const domain=this._getSearchItemDomain(activeItem);if(domain){groupActiveItemDomains.push(domain);}}
const groupDomain=Domain.or(groupActiveItemDomains);domains.push(groupDomain);}
if(this.display.searchPanel&&withSearchPanel){domains.push(this._getSearchPanelDomain());}
let domain;try{domain=Domain.and(domains);return params.raw?domain:domain.toList(this.domainEvalContext);}catch(error){throw new Error(_t("Failed to evaluate the domain: %(domain)s.\n%(error)s",{domain:domain.toString(),error:error.message,}));}}
_getFacets(){const facets=[];const groups=this._getGroups();for(const group of groups){const groupActiveItemDomains=[];const values=[];let title;let type;for(const activeItem of group.activeItems){const domain=this._getSearchItemDomain(activeItem,{withDateFilterDomain:true,});if(domain){groupActiveItemDomains.push(domain);}
const searchItem=this.searchItems[activeItem.searchItemId];switch(searchItem.type){case"field_property":case"field":{type="field";title=searchItem.description;for(const autocompleteValue of activeItem.autocompletValues){values.push(autocompleteValue.label);}
break;}
case"groupBy":{type="groupBy";values.push(searchItem.description);break;}
case"dateGroupBy":{type="groupBy";for(const intervalId of activeItem.intervalIds){const option=this.intervalOptions.find((o)=>o.id===intervalId);values.push(`${searchItem.description}: ${option.description}`);}
break;}
case"dateFilter":{type="filter";const periodDescription=this._getDateFilterDomain(searchItem,activeItem.generatorIds,"description");values.push(`${searchItem.description}: ${periodDescription}`);break;}
default:{type=searchItem.type;values.push(searchItem.description);}}}
const facet={groupId:group.id,type,values,separator:type==="groupBy"?">":_t("or"),};if(type==="field"){facet.title=title;}else{if(type==="groupBy"&&this.orderByCount){facet.icon=FACET_ICONS[this.orderByCount==="Asc"?"groupByAsc":"groupByDesc"];}else{facet.icon=FACET_ICONS[type];}
facet.color=FACET_COLORS[type];}
if(groupActiveItemDomains.length){facet.domain=Domain.or(groupActiveItemDomains).toString();}
facets.push(facet);}
return facets;}
_getFieldDomain(field,autocompleteValues){const domains=autocompleteValues.map(({label,value,operator,enforceEqual})=>{let domain;if(field.filterDomain){let filterDomain=field.filterDomain;if(enforceEqual){filterDomain=field.filterDomain.replaceAll("'ilike'","'='").replaceAll('"ilike"','"="');}
domain=new Domain(filterDomain).toList({self:label.trim(),raw_value:value,});}else if(field.type==="field"){domain=[[field.fieldName,operator,value]];}else if(field.type==="field_property"){domain=[field.propertyDomain,[`${field.fieldName}.${field.propertyFieldDefinition.name}`,operator,value],];}
return new Domain(domain);});return Domain.or(domains);}
_getFilterDomain(excludedFilterId){const domain=[];function addCondition(fieldName,valueMap){const ids=[];for(const[valueId,value]of valueMap){if(value.checked){ids.push(valueId);}}
if(ids.length){domain.push([fieldName,"in",ids]);}}
for(const filter of this.filters){if(filter.id===excludedFilterId){continue;}
const{fieldName,groups,values}=filter;if(groups){for(const group of groups.values()){addCondition(fieldName,group.values);}}else{addCondition(fieldName,values);}}
return domain;}
_getGroupBy(){const groups=this._getGroups();const groupBys=[];for(const group of groups){for(const activeItem of group.activeItems){const activeItemGroupBys=this._getSearchItemGroupBys(activeItem);if(activeItemGroupBys){groupBys.push(...activeItemGroupBys);}}}
const groupBy=groupBys.length?groupBys:this.globalGroupBy.slice();return typeof groupBy==="string"?[groupBy]:groupBy;}
_getGroupDomain(filter){const{fieldName,groups,enableCounters}=filter;const{type:fieldType}=this.searchViewFields[fieldName];if(!enableCounters||!groups){return{many2one:[],many2many:{},}[fieldType];}
let groupDomain=null;if(fieldType==="many2one"){for(const group of groups.values()){const valueIds=[];let active=false;for(const[valueId,value]of group.values){const{checked}=value;valueIds.push(valueId);if(checked){active=true;}}
if(active){if(groupDomain){groupDomain=[[0,"=",1]];break;}else{groupDomain=[[fieldName,"in",valueIds]];}}}}else if(fieldType==="many2many"){const checkedValueIds=new Map();groups.forEach(({values},groupId)=>{values.forEach(({checked},valueId)=>{if(checked){if(!checkedValueIds.has(groupId)){checkedValueIds.set(groupId,[]);}
checkedValueIds.get(groupId).push(valueId);}});});groupDomain={};for(const[gId,ids]of checkedValueIds.entries()){for(const groupId of groups.keys()){if(gId!==groupId){const key=JSON.stringify(groupId);if(!groupDomain[key]){groupDomain[key]=[];}
groupDomain[key].push([fieldName,"in",ids]);}}}}
return groupDomain;}
_getGroups(){const preGroups=[];for(const queryElem of this.query){const{searchItemId}=queryElem;const{groupId}=this.searchItems[searchItemId];let preGroup=preGroups.find((group)=>group.id===groupId);if(!preGroup){preGroup={id:groupId,queryElements:[]};preGroups.push(preGroup);}
preGroup.queryElements.push(queryElem);}
const groups=[];for(const preGroup of preGroups){const{queryElements,id}=preGroup;const activeItems=[];for(const queryElem of queryElements){const{searchItemId}=queryElem;let activeItem=activeItems.find(({searchItemId:id})=>id===searchItemId);if("generatorId"in queryElem){if(!activeItem){activeItem={searchItemId,generatorIds:[]};activeItems.push(activeItem);}
activeItem.generatorIds.push(queryElem.generatorId);}else if("intervalId"in queryElem){if(!activeItem){activeItem={searchItemId,intervalIds:[]};activeItems.push(activeItem);}
activeItem.intervalIds.push(queryElem.intervalId);}else if("autocompleteValue"in queryElem){if(!activeItem){activeItem={searchItemId,autocompletValues:[]};activeItems.push(activeItem);}
activeItem.autocompletValues.push(queryElem.autocompleteValue);}else{if(!activeItem){activeItem={searchItemId};activeItems.push(activeItem);}}}
for(const activeItem of activeItems){if("intervalIds"in activeItem){activeItem.intervalIds.sort((g1,g2)=>rankInterval(g1)-rankInterval(g2));}}
groups.push({id,activeItems});}
return groups;}
_getIrFilterDescription(params={}){const{description,isDefault,isShared,embeddedActionId}=params;const fns=this.env.__getContext__.callbacks;const localContext=Object.assign({},...fns.map((fn)=>fn()));const gs=this.env.__getOrderBy__.callbacks;let localOrderBy;if(gs.length){localOrderBy=gs.flatMap((g)=>g());}
const context=makeContext([this._getContext(),localContext]);const userContext=user.context;for(const key in context){if(key in userContext||/^search(panel)?_default_/.test(key)){delete context[key];}}
const domain=this._getDomain({raw:true,withGlobal:false}).toString();const groupBys=this._getGroupBy();const comparison=this.getFullComparison();const orderBy=localOrderBy||this._getOrderBy();const userId=isShared?false:user.userId;const preFavorite={description,isDefault,domain,context,groupBys,orderBy,userId,};const irFilter={name:description,action_id:this.env.config.actionId,model_id:this.resModel,domain,embedded_action_id:embeddedActionId,embedded_parent_res_id:this.globalContext.active_id||false,is_default:isDefault,sort:JSON.stringify(orderBy.map((o)=>`${o.name}${o.asc === false ? " desc" : ""}`)),user_id:userId,context:{group_by:groupBys,...context},};if(comparison){preFavorite.comparison=comparison;irFilter.context.comparison=comparison;}
return{preFavorite,irFilter};}
_getOrderBy(){const groups=this._getGroups();const orderBy=[];if(this.groupBy.length&&this.orderByCount){orderBy.push({name:"__count",asc:this.orderByCount==="Asc"});}
for(const group of groups){for(const activeItem of group.activeItems){const{searchItemId}=activeItem;const searchItem=this.searchItems[searchItemId];if(searchItem.type==="favorite"){orderBy.push(...searchItem.orderBy);}}}
return orderBy.length?orderBy:this.globalOrderBy;}
_getSearchItemContext(activeItem){const{searchItemId}=activeItem;const searchItem=this.searchItems[searchItemId];switch(searchItem.type){case"field":{let context={};if(searchItem.context){try{const self=activeItem.autocompletValues.map((autocompleValue)=>autocompleValue.value);context=evaluateExpr(searchItem.context,{self});if(typeof context!=="object"){throw Error();}}catch(error){throw new Error(_t("Failed to evaluate the context: %(context)s.\n%(error)s",{context:searchItem.context,error:error.message,}));}}
if(searchItem.isDefault&&searchItem.fieldType==="many2one"){context[`default_${searchItem.fieldName}`]=searchItem.defaultAutocompleteValue.value;}
return context;}
case"favorite":case"filter":{return makeContext([searchItem.context&&deepCopy(searchItem.context)]);}
default:{return null;}}}
_getSearchItemDomain(activeItem,options={}){const{searchItemId}=activeItem;const searchItem=this.searchItems[searchItemId];switch(searchItem.type){case"field_property":case"field":{return this._getFieldDomain(searchItem,activeItem.autocompletValues);}
case"dateFilter":{const{dateFilterId}=this._getActiveComparison()||{};if(options.withDateFilterDomain||!(this.searchMenuTypes.has("comparison")&&dateFilterId===searchItemId)){return this._getDateFilterDomain(searchItem,activeItem.generatorIds);}
return new Domain([]);}
case"filter":case"favorite":{return searchItem.domain;}
default:{return null;}}}
_getSearchItemGroupBys(activeItem){const{searchItemId}=activeItem;const searchItem=this.searchItems[searchItemId];switch(searchItem.type){case"dateGroupBy":{const{fieldName}=searchItem;return activeItem.intervalIds.map((intervalId)=>`${fieldName}:${intervalId}`);}
case"groupBy":{return[searchItem.fieldName];}
case"favorite":{return searchItem.groupBys;}
default:{return null;}}}
_getSelectedGeneratorIds(dateFilterId){const selectedOptionIds=[];for(const queryElem of this.query){if(queryElem.searchItemId===dateFilterId&&"generatorId"in queryElem){selectedOptionIds.push(queryElem.generatorId);}}
return selectedOptionIds;}
_getSearchPanelDomain(){return Domain.and([this._getCategoryDomain(),this._getFilterDomain()]);}
_importState(state){execute(arraytoMap,state,this);}
_irFilterToFavorite(irFilter){let userId=false;if(Array.isArray(irFilter.user_id)){userId=irFilter.user_id[0];}
const groupNumber=userId?FAVORITE_PRIVATE_GROUP:FAVORITE_SHARED_GROUP;const context=evaluateExpr(irFilter.context,user.context);let groupBys=[];if(context.group_by){groupBys=context.group_by;delete context.group_by;}
let comparison;if(context.comparison){comparison=context.comparison;if(typeof comparison.range==="string"){comparison.range=new Domain(comparison.range).toList();}
if(typeof comparison.comparisonRange==="string"){comparison.comparisonRange=new Domain(comparison.comparisonRange).toList();}
delete context.comparison;}
let sort;try{sort=JSON.parse(irFilter.sort);}catch(err){if(err instanceof SyntaxError){sort=[];}else{throw err;}}
const orderBy=sort.map((order)=>{let fieldName;let asc;const sqlNotation=order.split(" ");if(sqlNotation.length>1){fieldName=sqlNotation[0];asc=sqlNotation[1]==="asc";}else{fieldName=order[0]==="-"?order.slice(1):order;asc=order[0]==="-"?false:true;}
return{asc:asc,name:fieldName,};});const favorite={context,description:irFilter.name,domain:irFilter.domain,groupBys,groupNumber,orderBy,removable:true,serverSideId:irFilter.id,type:"favorite",userId,};if(irFilter.is_default){favorite.isDefault=irFilter.is_default;}
if(comparison){favorite.comparison=comparison;}
return favorite;}
async _notify(){if(this.blockNotification){return;}
this._reset();await this._reloadSections();this.trigger("update");}
async _reloadSections(){this.blockNotification=true;const searchDomain=this._getDomain({withSearchPanel:false});const searchDomainChanged=this.searchPanelInfo.shouldReload||JSON.stringify(this.searchDomain)!==JSON.stringify(searchDomain);this.searchDomain=searchDomain;const toFetch=(section)=>section.enableCounters||(searchDomainChanged&&!section.expand);const categoriesToFetch=this.categories.filter(toFetch);const filtersToFetch=this.filters.filter(toFetch);if(searchDomainChanged||Boolean(categoriesToFetch.length+filtersToFetch.length)){if(this.display.searchPanel){this.sectionsPromise=this._fetchSections(categoriesToFetch,filtersToFetch);if(this._shouldWaitForData(searchDomainChanged)){await this.sectionsPromise;}}
this.searchPanelInfo.shouldReload=!this.display.searchPanel;}
this.blockNotification=false;}
_reset(){delete this._comparison;this._context=null;this._domain=null;this._groupBy=null;this._orderBy=null;}
_shouldWaitForData(searchDomainChanged){if(this.categories.length&&this.filters.some((filter)=>filter.domain!=="[]")){return true;}
if(!this.searchDomain.length){return false;}
return[...this.sections.values()].some((section)=>!section.expand&&searchDomainChanged);}
__legacyParseSearchPanelArchAnyway(searchViewDescription,searchViewFields){if(this.searchPanelInfo){return;}
const parser=new SearchArchParser(searchViewDescription,searchViewFields);const{searchPanelInfo}=parser.parse();this.searchPanelInfo={...searchPanelInfo,loaded:false,shouldReload:false};}}
return __exports;});;

/* /web/static/src/search/search_panel/search_panel.js */
odoo.define('@web/search/search_panel/search_panel',['@web/core/dropdown/dropdown','@web/core/utils/hooks','@odoo/owl','@web/search/action_hook'],function(require){'use strict';let __exports={};const{Dropdown}=require("@web/core/dropdown/dropdown");const{useBus}=require("@web/core/utils/hooks");const{Component,onMounted,onWillStart,onWillUpdateProps,reactive,useEffect,useRef,useState,}=require("@odoo/owl");const{useSetupAction}=require("@web/search/action_hook");const isFilter=(s)=>s.type==="filter";const isActiveCategory=(s)=>s.type==="category"&&s.activeValueId;const nameOfCheckedValues=(values)=>{const names=[];for(const[,value]of values){if(value.checked){names.push(value.display_name);}}
return names;};const SearchPanel=__exports.SearchPanel=class SearchPanel extends Component{static template="web.SearchPanel";static props={};static components={Dropdown,};static subTemplates={section:"web.SearchPanel.Section",category:"web.SearchPanel.Category",filtersGroup:"web.SearchPanel.FiltersGroup",};setup(){this.state=useState({active:{},expanded:{},showMobileSearch:false,sidebarExpanded:!this.env.searchModel.searchPanelInfo.fold,});this.hasImportedState=false;this.root=useRef("root");this.scrollTop=0;this.dropdownStates={};this.width="10px";this.importState(this.env.searchPanelState);useBus(this.env.searchModel,"update",async()=>{await this.env.searchModel.sectionsPromise;this.updateActiveValues();this.render();});useEffect((el)=>{if(el&&this.hasImportedState){el.style["min-width"]=this.width;el.scroll({top:this.scrollTop});}},()=>[this.root.el]);useSetupAction({getGlobalState:()=>{return{searchPanel:this.exportState(),};},});onWillStart(async()=>{await this.env.searchModel.sectionsPromise;this.expandDefaultValue();this.updateActiveValues();});onWillUpdateProps(async()=>{await this.env.searchModel.sectionsPromise;this.updateActiveValues();});onMounted(()=>{this.updateGroupHeadersChecked();});}
get sections(){return this.env.searchModel.getSections((s)=>!s.empty);}
exportState(){const exported={expanded:this.state.expanded,scrollTop:this.root.el?.scrollTop||0,sidebarExpanded:this.state.sidebarExpanded,width:this.width,};return JSON.stringify(exported);}
importState(state){this.hasImportedState=Boolean(state);if(this.hasImportedState){this.state.expanded=state.expanded;this.scrollTop=state.scrollTop;this.state.sidebarExpanded=state.sidebarExpanded;this.width=state.width;}}
getDropdownState(sectionId){if(!this.dropdownStates[sectionId]){const state=reactive({isOpen:false,open:()=>(state.isOpen=true),close:()=>(state.isOpen=false),});this.dropdownStates[sectionId]=reactive(state);}
return this.dropdownStates[sectionId];}
expandDefaultValue(){if(this.hasImportedState){return;}
const categories=this.env.searchModel.getSections((s)=>s.type==="category");for(const category of categories){this.state.expanded[category.id]={};if(category.activeValueId){const ancestorIds=this.getAncestorValueIds(category,category.activeValueId);for(const ancestorId of ancestorIds){this.state.expanded[category.id][ancestorId]=true;}}}}
getAncestorValueIds(category,categoryValueId){const{parentId}=category.values.get(categoryValueId);return parentId?[...this.getAncestorValueIds(category,parentId),parentId]:[];}
getCategorySelection(){const activeCategories=this.env.searchModel.getSections(isActiveCategory);const selection=[];for(const category of activeCategories){const parentIds=this.getAncestorValueIds(category,category.activeValueId);const orderedCategoryNames=[...parentIds,category.activeValueId].map((valueId)=>category.values.get(valueId).display_name);selection.push({values:orderedCategoryNames,icon:category.icon,color:category.color,});}
return selection;}
getFilterSelection(){const filters=this.env.searchModel.getSections(isFilter);const selection=[];for(const{groups,values,icon,color}of filters){let filterValues;if(groups){filterValues=Object.keys(groups).map((groupId)=>nameOfCheckedValues(groups[groupId].values)).flat();}else if(values){filterValues=nameOfCheckedValues(values);}
if(filterValues.length){selection.push({values:filterValues,icon,color});}}
return selection;}
hasSelection(sectionId=0){if(sectionId){const sectionState=this.state.active[sectionId];if(sectionState instanceof Object){return Object.values(sectionState).some((val)=>val);}
return Boolean(sectionState);}
return Object.keys(this.state.active).some((key)=>this.hasSelection(key));}
clearSelection(sectionId=0){const sectionIds=sectionId?[sectionId]:Object.keys(this.state.active).map(Number);this.env.searchModel.clearSections(sectionIds);}
async toggleCategory(category,value){if(value.childrenIds.length){const categoryState=this.state.expanded[category.id];if(categoryState[value.id]&&category.activeValueId===value.id){delete categoryState[value.id];}else{categoryState[value.id]=true;}}else{this.getDropdownState(category.id).close();}
if(category.activeValueId!==value.id){this.env.searchModel.toggleCategoryValue(category.id,value.id);}}
toggleSidebar(){this.state.sidebarExpanded=!this.state.sidebarExpanded;}
toggleFilterGroup(filterId,{values}){const valueIds=[];const checked=[...values.values()].every((value)=>this.state.active[filterId][value.id]);values.forEach(({id})=>{valueIds.push(id);this.state.active[filterId][id]=!checked;});this.env.searchModel.toggleFilterValues(filterId,valueIds,!checked);}
toggleFilterValue(filterId,valueId,{currentTarget}){this.state.active[filterId][valueId]=currentTarget.checked;this.updateGroupHeadersChecked();this.env.searchModel.toggleFilterValues(filterId,[valueId]);}
updateActiveValues(){for(const section of this.sections){if(section.type==="category"){this.state.active[section.id]=section.activeValueId;}else{this.state.active[section.id]={};if(section.groups){for(const group of section.groups.values()){for(const value of group.values.values()){this.state.active[section.id][value.id]=value.checked;}}}
if(section&&section.values){for(const value of section.values.values()){this.state.active[section.id][value.id]=value.checked;}}}}}
updateGroupHeadersChecked(){const groups=document.querySelectorAll(".o_search_panel_filter_group");for(const group of groups){const header=group.querySelector(":scope .o_search_panel_group_header input");const vals=[...group.querySelectorAll(":scope .o_search_panel_filter_value input")];header.checked=false;header.indeterminate=false;if(vals.every((v)=>v.checked)){header.checked=true;}else if(vals.some((v)=>v.checked)){header.indeterminate=true;}}}
_onStartResize(ev){if(ev.button!==0){return;}
const initialX=ev.pageX;const initialWidth=this.root.el.offsetWidth;const resizeStoppingEvents=["keydown","pointerdown","pointerup"];const resizePanel=(ev)=>{ev.preventDefault();ev.stopPropagation();const maxWidth=Math.max(0.5*window.innerWidth,initialWidth);const delta=ev.pageX-initialX;const newWidth=Math.min(maxWidth,Math.max(10,initialWidth+delta));this.width=`${newWidth}px`;this.root.el.style["min-width"]=this.width;};document.addEventListener("pointermove",resizePanel,true);const stopResize=(ev)=>{if(ev.type==="pointerdown"&&ev.button===0){return;}
ev.preventDefault();ev.stopPropagation();document.removeEventListener("pointermove",resizePanel,true);resizeStoppingEvents.forEach((stoppingEvent)=>{document.removeEventListener(stoppingEvent,stopResize,true);});document.activeElement.blur();};resizeStoppingEvents.forEach((stoppingEvent)=>{document.addEventListener(stoppingEvent,stopResize,true);});}}
return __exports;});;

/* /web/static/src/search/utils/dates.js */
odoo.define('@web/search/utils/dates',['@web/core/l10n/translation','@web/core/domain','@web/core/l10n/dates','@web/core/l10n/localization','@web/core/utils/numbers','@web/core/utils/objects'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{Domain}=require("@web/core/domain");const{serializeDate,serializeDateTime}=require("@web/core/l10n/dates");const{localization}=require("@web/core/l10n/localization");const{clamp}=require("@web/core/utils/numbers");const{pick}=require("@web/core/utils/objects");const QUARTERS=__exports.QUARTERS={1:{description:_t("Q1"),coveredMonths:[1,2,3]},2:{description:_t("Q2"),coveredMonths:[4,5,6]},3:{description:_t("Q3"),coveredMonths:[7,8,9]},4:{description:_t("Q4"),coveredMonths:[10,11,12]},};const QUARTER_OPTIONS=__exports.QUARTER_OPTIONS={fourth_quarter:{id:"fourth_quarter",groupNumber:1,description:QUARTERS[4].description,setParam:{quarter:4},granularity:"quarter",},third_quarter:{id:"third_quarter",groupNumber:1,description:QUARTERS[3].description,setParam:{quarter:3},granularity:"quarter",},second_quarter:{id:"second_quarter",groupNumber:1,description:QUARTERS[2].description,setParam:{quarter:2},granularity:"quarter",},first_quarter:{id:"first_quarter",groupNumber:1,description:QUARTERS[1].description,setParam:{quarter:1},granularity:"quarter",},};const DEFAULT_INTERVAL=__exports.DEFAULT_INTERVAL="month";const INTERVAL_OPTIONS=__exports.INTERVAL_OPTIONS={year:{description:_t("Year"),id:"year",groupNumber:1},quarter:{description:_t("Quarter"),id:"quarter",groupNumber:1},month:{description:_t("Month"),id:"month",groupNumber:1},week:{description:_t("Week"),id:"week",groupNumber:1},day:{description:_t("Day"),id:"day",groupNumber:1},};const COMPARISON_OPTIONS=__exports.COMPARISON_OPTIONS={previous_period:{description:_t("Previous Period"),id:"previous_period",},previous_year:{description:_t("Previous Year"),id:"previous_year",plusParam:{years:-1},},};const PER_YEAR=__exports.PER_YEAR={year:1,quarter:4,month:12,};__exports.constructDateDomain=constructDateDomain;function constructDateDomain(referenceMoment,searchItem,selectedOptionIds,comparisonOptionId){let plusParam;let selectedOptions;if(comparisonOptionId){[plusParam,selectedOptions]=getComparisonParams(referenceMoment,searchItem,selectedOptionIds,comparisonOptionId);}else{selectedOptions=getSelectedOptions(referenceMoment,searchItem,selectedOptionIds);}
if("withDomain"in selectedOptions){return{description:selectedOptions.withDomain[0].description,domain:Domain.and([selectedOptions.withDomain[0].domain,searchItem.domain]),};}
const yearOptions=selectedOptions.year;const otherOptions=[...(selectedOptions.quarter||[]),...(selectedOptions.month||[])];sortPeriodOptions(yearOptions);sortPeriodOptions(otherOptions);const ranges=[];const{fieldName,fieldType}=searchItem;for(const yearOption of yearOptions){const constructRangeParams={referenceMoment,fieldName,fieldType,plusParam,};if(otherOptions.length){for(const option of otherOptions){const setParam=Object.assign({},yearOption.setParam,option?option.setParam:{});const{granularity}=option;const range=constructDateRange(Object.assign({granularity,setParam},constructRangeParams));ranges.push(range);}}else{const{granularity,setParam}=yearOption;const range=constructDateRange(Object.assign({granularity,setParam},constructRangeParams));ranges.push(range);}}
let domain=Domain.combine(ranges.map((range)=>range.domain),"OR");domain=Domain.and([domain,searchItem.domain]);const description=ranges.map((range)=>range.description).join("/");return{domain,description};}
__exports.constructDateRange=constructDateRange;function constructDateRange(params){const{referenceMoment,fieldName,fieldType,granularity,setParam,plusParam}=params;if("quarter"in setParam){setParam.month=QUARTERS[setParam.quarter].coveredMonths[0];delete setParam.quarter;}
const date=referenceMoment.set(setParam).plus(plusParam||{});const leftDate=date.startOf(granularity);const rightDate=date.endOf(granularity);let leftBound;let rightBound;if(fieldType==="date"){leftBound=serializeDate(leftDate);rightBound=serializeDate(rightDate);}else{leftBound=serializeDateTime(leftDate);rightBound=serializeDateTime(rightDate);}
const domain=new Domain(["&",[fieldName,">=",leftBound],[fieldName,"<=",rightBound]]);const descriptions=[date.toFormat("yyyy")];const method=localization.direction==="rtl"?"push":"unshift";if(granularity==="month"){descriptions[method](date.toFormat("MMMM"));}else if(granularity==="quarter"){const quarter=date.quarter;descriptions[method](QUARTERS[quarter].description.toString());}
const description=descriptions.join(" ");return{domain,description};}
__exports.getComparisonOptions=getComparisonOptions;function getComparisonOptions(){return getOptionsWithDescriptions(COMPARISON_OPTIONS);}
__exports.getComparisonParams=getComparisonParams;function getComparisonParams(referenceMoment,searchItem,selectedOptionIds,comparisonOptionId){const comparisonOption=COMPARISON_OPTIONS[comparisonOptionId];const selectedOptions=getSelectedOptions(referenceMoment,searchItem,selectedOptionIds);if(comparisonOption.plusParam){return[comparisonOption.plusParam,selectedOptions];}
const plusParam={};let globalGranularity="year";if(selectedOptions.month){globalGranularity="month";}else if(selectedOptions.quarter){globalGranularity="quarter";}
const granularityFactor=PER_YEAR[globalGranularity];const years=selectedOptions.year.map((o)=>o.setParam.year);const yearMin=Math.min(...years);const yearMax=Math.max(...years);let optionMin=0;let optionMax=0;if(selectedOptions.quarter){const quarters=selectedOptions.quarter.map((o)=>o.setParam.quarter);if(globalGranularity==="month"){delete selectedOptions.quarter;for(const quarter of quarters){for(const month of QUARTERS[quarter].coveredMonths){const monthOption=selectedOptions.month.find((o)=>o.setParam.month===month);if(!monthOption){selectedOptions.month.push({setParam:{month},granularity:"month",});}}}}else{optionMin=Math.min(...quarters);optionMax=Math.max(...quarters);}}
if(selectedOptions.month){const months=selectedOptions.month.map((o)=>o.setParam.month);optionMin=Math.min(...months);optionMax=Math.max(...months);}
const num=-1+granularityFactor*(yearMin-yearMax)+optionMin-optionMax;const key=globalGranularity==="year"?"years":globalGranularity==="month"?"months":"quarters";plusParam[key]=num;return[plusParam,selectedOptions];}
__exports.getIntervalOptions=getIntervalOptions;function getIntervalOptions(){return getOptionsWithDescriptions(INTERVAL_OPTIONS);}
__exports.getOptionsWithDescriptions=getOptionsWithDescriptions;function getOptionsWithDescriptions(OPTIONS){const options=[];for(const option of Object.values(OPTIONS)){options.push(Object.assign({},option,{description:option.description.toString()}));}
return options;}
__exports.getPeriodOptions=getPeriodOptions;function getPeriodOptions(referenceMoment,optionsParams){return[...getMonthPeriodOptions(referenceMoment,optionsParams),...getQuarterPeriodOptions(optionsParams),...getYearPeriodOptions(referenceMoment,optionsParams),...getCustomPeriodOptions(optionsParams),];}
__exports.toGeneratorId=toGeneratorId;function toGeneratorId(unit,offset){if(!offset){return unit;}
const sep=offset>0?"+":"-";const val=Math.abs(offset);return`${unit}${sep}${val}`;}
function getMonthPeriodOptions(referenceMoment,optionsParams){const{startYear,endYear,startMonth,endMonth}=optionsParams;return[...Array(endMonth-startMonth+1).keys()].map((i)=>{const monthOffset=startMonth+i;const date=referenceMoment.plus({months:monthOffset,years:clamp(0,startYear,endYear),});const yearOffset=date.year-referenceMoment.year;return{id:toGeneratorId("month",monthOffset),defaultYearId:toGeneratorId("year",clamp(yearOffset,startYear,endYear)),description:date.toFormat("MMMM"),granularity:"month",groupNumber:1,plusParam:{months:monthOffset},};}).reverse();}
function getQuarterPeriodOptions(optionsParams){const{startYear,endYear}=optionsParams;const defaultYearId=toGeneratorId("year",clamp(0,startYear,endYear));return Object.values(QUARTER_OPTIONS).map((quarter)=>({...quarter,defaultYearId,}));}
function getYearPeriodOptions(referenceMoment,optionsParams){const{startYear,endYear}=optionsParams;return[...Array(endYear-startYear+1).keys()].map((i)=>{const offset=startYear+i;const date=referenceMoment.plus({years:offset});return{id:toGeneratorId("year",offset),description:date.toFormat("yyyy"),granularity:"year",groupNumber:2,plusParam:{years:offset},};}).reverse();}
function getCustomPeriodOptions(optionsParams){const{customOptions}=optionsParams;return customOptions.map((option)=>({id:option.id,description:option.description,granularity:"withDomain",groupNumber:3,domain:option.domain,}));}
__exports.getSelectedOptions=getSelectedOptions;function getSelectedOptions(referenceMoment,searchItem,selectedOptionIds){const selectedOptions={year:[]};const periodOptions=getPeriodOptions(referenceMoment,searchItem.optionsParams);for(const optionId of selectedOptionIds){const option=periodOptions.find((option)=>option.id===optionId);const granularity=option.granularity;if(!selectedOptions[granularity]){selectedOptions[granularity]=[];}
if(option.domain){selectedOptions[granularity].push(pick(option,"domain","description"));}else{const setParam=getSetParam(option,referenceMoment);selectedOptions[granularity].push({granularity,setParam});}}
return selectedOptions;}
__exports.getSetParam=getSetParam;function getSetParam(periodOption,referenceMoment){if(periodOption.granularity==="quarter"){return periodOption.setParam;}
const date=referenceMoment.plus(periodOption.plusParam);const granularity=periodOption.granularity;const setParam={[granularity]:date[granularity]};return setParam;}
__exports.rankInterval=rankInterval;function rankInterval(intervalOptionId){return Object.keys(INTERVAL_OPTIONS).indexOf(intervalOptionId);}
__exports.sortPeriodOptions=sortPeriodOptions;function sortPeriodOptions(options){options.sort((o1,o2)=>{var _a,_b;const granularity1=o1.granularity;const granularity2=o2.granularity;if(granularity1===granularity2){return(((_a=o1.setParam[granularity1])!==null&&_a!==void 0?_a:0)-
((_b=o2.setParam[granularity1])!==null&&_b!==void 0?_b:0));}
return granularity1<granularity2?-1:1;});}
__exports.yearSelected=yearSelected;function yearSelected(selectedOptionIds){return selectedOptionIds.some((optionId)=>optionId.startsWith("year"));}
return __exports;});;

/* /web/static/src/search/utils/group_by.js */
odoo.define('@web/search/utils/group_by',['@web/search/utils/dates'],function(require){'use strict';let __exports={};const{DEFAULT_INTERVAL,INTERVAL_OPTIONS}=require("@web/search/utils/dates");function errorMsg(descr){return`Invalid groupBy description: ${descr}`;}
__exports.getGroupBy=getGroupBy;function getGroupBy(descr,fields){let fieldName;let interval;let spec;[fieldName,interval]=descr.split(":");if(!fieldName){throw Error();}
if(fields){if(!fields[fieldName]&&!fieldName.includes(".")){throw Error(errorMsg(descr));}
const fieldType=fields[fieldName]?.type;if(["date","datetime"].includes(fieldType)){if(!interval){interval=DEFAULT_INTERVAL;}else if(!Object.keys(INTERVAL_OPTIONS).includes(interval)){throw Error(errorMsg(descr));}
spec=`${fieldName}:${interval}`;}else if(interval){throw Error(errorMsg(descr));}else{spec=fieldName;interval=null;}}else{if(interval){if(!Object.keys(INTERVAL_OPTIONS).includes(interval)){throw Error(errorMsg(descr));}
spec=`${fieldName}:${interval}`;}else{spec=fieldName;interval=null;}}
return{fieldName,interval,spec,toJSON(){return spec;},};}
return __exports;});;

/* /web/static/src/search/utils/misc.js */
odoo.define('@web/search/utils/misc',[],function(require){'use strict';let __exports={};const FACET_ICONS=__exports.FACET_ICONS={filter:"fa fa-filter",groupBy:"oi oi-group",groupByAsc:"fa fa-sort-numeric-asc",groupByDesc:"fa fa-sort-numeric-desc",favorite:"fa fa-star",comparison:"fa fa-adjust",};const FACET_COLORS=__exports.FACET_COLORS={filter:"primary",groupBy:"action",favorite:"warning",comparison:"danger",};const GROUPABLE_TYPES=__exports.GROUPABLE_TYPES=["boolean","char","date","datetime","integer","many2one","many2many","selection","tags",];return __exports;});;

/* /web/static/src/search/utils/order_by.js */
odoo.define('@web/search/utils/order_by',[],function(require){'use strict';let __exports={};__exports.orderByToString=orderByToString;function orderByToString(orderBy){return orderBy.map((o)=>`${o.name} ${o.asc ? "ASC" : "DESC"}`).join(", ");}
__exports.stringToOrderBy=stringToOrderBy;function stringToOrderBy(string){if(!string){return[];}
return string.split(",").map((order)=>{const splitOrder=order.trim().split(" ");if(splitOrder.length===2){return{name:splitOrder[0],asc:splitOrder[1].toLowerCase()==="asc",};}else{return{name:splitOrder[0],asc:true,};}});}
return __exports;});;

/* /web/static/src/search/with_search/with_search.js */
odoo.define('@web/search/with_search/with_search',['@odoo/owl','@web/search/action_hook','@web/search/search_model','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{Component,onWillStart,onWillUpdateProps,toRaw,useSubEnv}=require("@odoo/owl");const{CallbackRecorder,useSetupAction}=require("@web/search/action_hook");const{SearchModel}=require("@web/search/search_model");const{useBus,useService}=require("@web/core/utils/hooks");const SEARCH_KEYS=__exports.SEARCH_KEYS=["comparison","context","domain","groupBy","orderBy"];const WithSearch=__exports.WithSearch=class WithSearch extends Component{static template="web.WithSearch";static props={slots:Object,SearchModel:{type:Function,optional:true},resModel:String,globalState:{type:Object,optional:true},searchModelArgs:{type:Object,optional:true},display:{type:Object,optional:true},comparison:{type:[Object,{value:null}],optional:true},context:{type:Object,optional:true},domain:{type:Array,element:[String,Array],optional:true},groupBy:{type:Array,element:String,optional:true},orderBy:{type:Array,element:Object,optional:true},searchViewArch:{type:String,optional:true},searchViewFields:{type:Object,optional:true},searchViewId:{type:[Number,Boolean],optional:true},irFilters:{type:Array,element:Object,optional:true},loadIrFilters:{type:Boolean,optional:true},activateFavorite:{type:Boolean,optional:true},dynamicFilters:{type:Array,element:Object,optional:true},hideCustomGroupBy:{type:Boolean,optional:true},searchMenuTypes:{type:Array,element:String,optional:true},canOrderByCount:{type:Boolean,optional:true},};setup(){if(!this.env.__getContext__){useSubEnv({__getContext__:new CallbackRecorder()});}
if(!this.env.__getOrderBy__){useSubEnv({__getOrderBy__:new CallbackRecorder()});}
const SearchModelClass=this.props.SearchModel||SearchModel;this.searchModel=new SearchModelClass(this.env,{orm:useService("orm"),view:useService("view"),field:useService("field"),name:useService("name"),dialog:useService("dialog"),},this.props.searchModelArgs);const searchPanelState=this.props.globalState?.searchPanel?JSON.parse(this.props.globalState?.searchPanel):null;useSubEnv({searchModel:this.searchModel,searchPanelState});useBus(this.searchModel,"update",this.render);useSetupAction({getGlobalState:()=>{return{searchModel:JSON.stringify(this.searchModel.exportState()),};},});onWillStart(async()=>{const config={...toRaw(this.props)};if(config.globalState&&config.globalState.searchModel){config.state=JSON.parse(config.globalState.searchModel);delete config.globalState;}
await this.searchModel.load(config);});onWillUpdateProps(async(nextProps)=>{const config={};for(const key of SEARCH_KEYS){if(nextProps[key]!==undefined){config[key]=nextProps[key];}}
await this.searchModel.reload(config);});}}
return __exports;});;

/* /web/static/src/views/view.js */
odoo.define('@web/views/view',['@web/core/debug/debug_context','@web/core/py_js/py','@web/core/registry','@web/core/utils/concurrency','@web/core/utils/hooks','@web/core/utils/objects','@web/core/utils/strings','@web/core/utils/xml','@web/search/layout','@web/search/with_search/with_search','@web/views/view_hook','@web/views/utils','@web/core/assets','@web/core/browser/cookie','@odoo/owl','@web/session'],function(require){'use strict';let __exports={};const{useDebugCategory}=require("@web/core/debug/debug_context");const{evaluateBooleanExpr}=require("@web/core/py_js/py");const{registry}=require("@web/core/registry");const{KeepLast}=require("@web/core/utils/concurrency");const{useService}=require("@web/core/utils/hooks");const{deepCopy,pick}=require("@web/core/utils/objects");const{nbsp}=require("@web/core/utils/strings");const{parseXML}=require("@web/core/utils/xml");const{extractLayoutComponents}=require("@web/search/layout");const{WithSearch}=require("@web/search/with_search/with_search");const{useActionLinks}=require("@web/views/view_hook");const{computeViewClassName}=require("@web/views/utils");const{loadBundle}=require("@web/core/assets");const{cookie}=require("@web/core/browser/cookie");const{Component,markRaw,onWillUpdateProps,onWillStart,toRaw,useSubEnv,reactive,}=require("@odoo/owl");const{session}=require("@web/session");const viewRegistry=registry.category("views");viewRegistry.addValidation({type:{validate:(t)=>t in session.view_info},Controller:{validate:(c)=>c.prototype instanceof Component},"*":true,});__exports.getDefaultConfig=getDefaultConfig;function getDefaultConfig(){let displayName;const config={actionId:false,actionType:false,embeddedActions:[],currentEmbeddedActionId:false,parentActionId:false,actionFlags:{},breadcrumbs:reactive([{get name(){return displayName;},},]),disableSearchBarAutofocus:false,getDisplayName:()=>displayName,historyBack:()=>{},pagerProps:{},setDisplayName:(newDisplayName)=>{displayName=newDisplayName;config.breadcrumbs.push(undefined);config.breadcrumbs.pop();},viewSwitcherEntries:[],views:[],};return config;}
const ViewNotFoundError=__exports.ViewNotFoundError=class ViewNotFoundError extends Error{}
const CALLBACK_RECORDER_NAMES=["__beforeLeave__","__getGlobalState__","__getLocalState__","__getContext__","__getOrderBy__",];const STANDARD_PROPS=["resModel","type","jsClass","arch","fields","relatedModels","viewId","views","actionMenus","loadActionMenus","searchViewArch","searchViewFields","searchViewId","irFilters","loadIrFilters","comparison","context","domain","groupBy","orderBy","useSampleModel","noContentHelp","className","display","globalState","activateFavorite","dynamicFilters","hideCustomGroupBy","searchMenuTypes",...CALLBACK_RECORDER_NAMES,"searchPanel","searchModel",];const ACTIONS=["create","delete","edit","group_create","group_delete","group_edit"];const View=__exports.View=class View extends Component{static _download=async function(){};static template="web.View";static components={WithSearch};static searchMenuTypes=["filter","groupBy","favorite"];static canOrderByCount=false;static defaultProps={display:{},context:{},loadActionMenus:false,loadIrFilters:false,className:"",};static props={"*":true,};setup(){const{arch,fields,resModel,searchViewArch,searchViewFields,type}=this.props;if(!resModel){throw Error(`View props should have a "resModel" key`);}
if(!type){throw Error(`View props should have a "type" key`);}
if((arch&&!fields)||(!arch&&fields)){throw new Error(`"arch" and "fields" props must be given together`);}
if((searchViewArch&&!searchViewFields)||(!searchViewArch&&searchViewFields)){throw new Error(`"searchViewArch" and "searchViewFields" props must be given together`);}
this.viewService=useService("view");this.withSearchProps=null;useSubEnv({keepLast:new KeepLast(),config:{...getDefaultConfig(),...this.env.config,},...Object.fromEntries(CALLBACK_RECORDER_NAMES.map((name)=>[name,this.props[name]||null])),});this.handleActionLinks=useActionLinks({resModel});onWillStart(()=>this.loadView(this.props));onWillUpdateProps((nextProps)=>this.onWillUpdateProps(nextProps));useDebugCategory("view",{component:this});}
async loadView(props){const type=props.type;if(!session.view_info[type]){throw new Error(`Invalid view type: ${type}`);}
let{viewId,searchViewId}=props;const views=deepCopy(props.views||this.env.config.views);const view=views.find((v)=>v[1]===type)||[];if(view.length){view[0]=viewId!==undefined?viewId:view[0];viewId=view[0];}else{view.push(viewId||false,type);views.push(view);}
const searchView=views.find((v)=>v[1]==="search");if(searchView){searchView[0]=searchViewId!==undefined?searchViewId:searchView[0];searchViewId=searchView[0];}else if(searchViewId!==undefined){views.push([searchViewId,"search"]);}
const{context,resModel,loadActionMenus,loadIrFilters}=props;let{arch,fields,relatedModels,searchViewArch,searchViewFields,irFilters,actionMenus,}=props;const loadView=!arch||(!actionMenus&&loadActionMenus);const loadSearchView=(searchViewId!==undefined&&!searchViewArch)||(!irFilters&&loadIrFilters);let viewDescription={viewId,resModel,type};let searchViewDescription;if(loadView||loadSearchView){const options={actionId:this.env.config.actionId,loadActionMenus,loadIrFilters,};if(this.env.config.currentEmbeddedActionId){options.embeddedActionId=this.env.config.currentEmbeddedActionId;options.embeddedParentResId=context.active_id;}
const result=await this.viewService.loadViews({context,resModel,views},options);viewDescription=result.views[type];searchViewDescription=result.views.search;if(loadSearchView){searchViewId=searchViewId||searchViewDescription.id;if(!searchViewArch){searchViewArch=searchViewDescription.arch;searchViewFields=result.fields;}
if(!irFilters){irFilters=searchViewDescription.irFilters;}}
this.env.config.views=views;fields=fields||markRaw(result.fields);relatedModels=relatedModels||markRaw(result.relatedModels);}
if(!arch){arch=viewDescription.arch;}
if(!actionMenus){actionMenus=viewDescription.actionMenus;}
const archXmlDoc=parseXML(arch.replace(/&amp;nbsp;/g,nbsp));for(const action of ACTIONS){if(action in this.props.context&&!this.props.context[action]){archXmlDoc.setAttribute(action,"0");}}
const jsClass=archXmlDoc.hasAttribute("js_class")?archXmlDoc.getAttribute("js_class"):props.jsClass||type;if(!viewRegistry.contains(jsClass)){await loadBundle(cookie.get("color_scheme")==="dark"?"web.assets_backend_lazy_dark":"web.assets_backend_lazy");}
const descr=viewRegistry.get(jsClass);const sample=archXmlDoc.getAttribute("sample");const className=computeViewClassName(type,archXmlDoc,["o_view_controller",...(props.className||"").split(" "),]);Object.assign(this.env.config,{rawArch:arch,viewArch:archXmlDoc,viewId:viewDescription.id,viewType:type,viewSubType:jsClass,noBreadcrumbs:props.noBreadcrumbs,...extractLayoutComponents(descr),});const info={actionMenus,mode:props.display.mode,irFilters,searchViewArch,searchViewFields,searchViewId,};const viewProps={info,arch:archXmlDoc,fields,relatedModels,resModel,useSampleModel:false,className,};if(viewDescription.custom_view_id){viewProps.info.customViewId=viewDescription.custom_view_id;}
if(props.globalState){viewProps.globalState=props.globalState;}
if("useSampleModel"in props){viewProps.useSampleModel=props.useSampleModel;}else if(sample){viewProps.useSampleModel=evaluateBooleanExpr(sample);}
for(const key in props){if(!STANDARD_PROPS.includes(key)){viewProps[key]=props[key];}}
const{noContentHelp}=props;if(noContentHelp){viewProps.info.noContentHelp=noContentHelp;}
const searchMenuTypes=props.searchMenuTypes||descr.searchMenuTypes||this.constructor.searchMenuTypes;viewProps.searchMenuTypes=searchMenuTypes;const canOrderByCount=descr.canOrderByCount||this.constructor.canOrderByCount;const finalProps=descr.props?descr.props(viewProps,descr,this.env.config):viewProps;this.Controller=descr.Controller;this.componentProps=finalProps;this.withSearchProps={...toRaw(props),hideCustomGroupBy:props.hideCustomGroupBy||descr.hideCustomGroupBy,searchMenuTypes,canOrderByCount,SearchModel:descr.SearchModel,};if(searchViewId!==undefined){this.withSearchProps.searchViewId=searchViewId;}
if(searchViewArch){this.withSearchProps.searchViewArch=searchViewArch;this.withSearchProps.searchViewFields=searchViewFields;}
if(irFilters){this.withSearchProps.irFilters=irFilters;}
if(descr.display){const viewDisplay=deepCopy(descr.display);const display={...this.withSearchProps.display};for(const key in viewDisplay){if(typeof display[key]==="object"){Object.assign(display[key],viewDisplay[key]);}else if(!(key in display)||display[key]){display[key]=viewDisplay[key];}}
this.withSearchProps.display=display;}
for(const key in this.withSearchProps){if(!(key in WithSearch.props)){delete this.withSearchProps[key];}}}
onWillUpdateProps(nextProps){const oldProps=pick(this.props,"arch","type","resModel");const newProps=pick(nextProps,"arch","type","resModel");if(JSON.stringify(oldProps)!==JSON.stringify(newProps)){return this.loadView(nextProps);}
const{comparison,context,domain,groupBy,orderBy}=nextProps;Object.assign(this.withSearchProps,{comparison,context,domain,groupBy,orderBy});}}
return __exports;});;

/* /web/static/src/views/view_hook.js */
odoo.define('@web/views/view_hook',['@web/core/registry','@web/core/utils/hooks','@web/core/browser/browser','@web/core/py_js/py','@odoo/owl'],function(require){'use strict';let __exports={};const{registry}=require("@web/core/registry");const{useService}=require("@web/core/utils/hooks");const{browser}=require("@web/core/browser/browser");const{evaluateExpr}=require("@web/core/py_js/py");const{useComponent,useEffect,xml}=require("@odoo/owl");__exports.useViewArch=useViewArch;function useViewArch(arch,params={}){const CATEGORY="__processed_archs__";arch=arch.trim();const processedRegistry=registry.category(CATEGORY);let processedArch;if(!processedRegistry.contains(arch)){processedArch={};processedRegistry.add(arch,processedArch);}else{processedArch=processedRegistry.get(arch);}
const{compile,extract}=params;if(!("template"in processedArch)&&compile){processedArch.template=xml`${compile(arch)}`;}
if(!("extracted"in processedArch)&&extract){processedArch.extracted=extract(arch);}
return processedArch;}
__exports.useActionLinks=useActionLinks;function useActionLinks({resModel,reload}){const component=useComponent();const keepLast=component.env.keepLast;const orm=useService("orm");const{doAction}=useService("action");async function handler(ev){ev.preventDefault();ev.stopPropagation();let target=ev.target;if(target.tagName!=="A"){target=target.closest("a");}
const data=target.dataset;if(data.method!==undefined&&data.model!==undefined){const options={};if(data.reloadOnClose){options.onClose=reload||(()=>component.render());}
const action=await keepLast.add(orm.call(data.model,data.method));if(action!==undefined){keepLast.add(Promise.resolve(doAction(action,options)));}}else if(target.getAttribute("name")){const options={};if(data.context){options.additionalContext=evaluateExpr(data.context);}
keepLast.add(doAction(target.getAttribute("name"),options));}else{let views;const resId=data.resid?parseInt(data.resid,10):null;if(data.views){views=evaluateExpr(data.views);}else{views=resId?[[false,"form"]]:[[false,"list"],[false,"form"],];}
const action={name:target.getAttribute("title")||target.textContent.trim(),type:"ir.actions.act_window",res_model:data.model||resModel,target:"current",views,domain:data.domain?evaluateExpr(data.domain):[],};if(resId){action.res_id=resId;}
const options={};if(data.context){options.additionalContext=evaluateExpr(data.context);}
keepLast.add(doAction(action,options));}}
return(ev)=>{const a=ev.target.closest(`a[type="action"]`);if(a&&ev.currentTarget.contains(a)){handler(ev);}};}
__exports.useBounceButton=useBounceButton;function useBounceButton(containerRef,shouldBounce){let timeout;const ui=useService("ui");useEffect((containerEl)=>{if(!containerEl){return;}
const handler=(ev)=>{const button=ui.activeElement.querySelector("[data-bounce-button]");if(button&&shouldBounce(ev.target)){button.classList.add("o_catch_attention");browser.clearTimeout(timeout);timeout=browser.setTimeout(()=>{button.classList.remove("o_catch_attention");},400);}};containerEl.addEventListener("click",handler);return()=>containerEl.removeEventListener("click",handler);},()=>[containerRef.el]);}
return __exports;});;

/* /web/static/src/webclient/actions/action_dialog.js */
odoo.define('@web/webclient/actions/action_dialog',['@web/core/dialog/dialog','@web/core/debug/debug_menu','@web/core/debug/debug_context','@odoo/owl'],function(require){'use strict';let __exports={};const{Dialog}=require("@web/core/dialog/dialog");const{DebugMenu}=require("@web/core/debug/debug_menu");const{useOwnDebugContext}=require("@web/core/debug/debug_context");const{useEffect}=require("@odoo/owl");const ActionDialog=__exports.ActionDialog=class ActionDialog extends Dialog{static components={...Dialog.components,DebugMenu};static template="web.ActionDialog";static props={...Dialog.props,close:Function,slots:{optional:true},ActionComponent:{optional:true},actionProps:{optional:true},actionType:{optional:true},title:{optional:true},};static defaultProps={...Dialog.defaultProps,withBodyPadding:false,};setup(){super.setup();useOwnDebugContext();useEffect(()=>{if(this.modalRef.el.querySelector(".modal-footer")?.childElementCount>1){const defaultButton=this.modalRef.el.querySelector(".modal-footer button.o-default-button");defaultButton.classList.add("d-none");}},()=>[]);}}
return __exports;});;

/* /web/static/src/webclient/actions/reports/utils.js */
odoo.define('@web/webclient/actions/reports/utils',['@web/core/l10n/translation','@web/core/network/download'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{download}=require("@web/core/network/download");__exports.getReportUrl=getReportUrl;function getReportUrl(action,type,userContext){let url=`/report/${type}/${action.report_name}`;const actionContext=action.context||{};if(action.data&&JSON.stringify(action.data)!=="{}"){const options=encodeURIComponent(JSON.stringify(action.data));const context=encodeURIComponent(JSON.stringify(actionContext));url+=`?options=${options}&context=${context}`;}else{if(actionContext.active_ids){url+=`/${actionContext.active_ids.join(",")}`;}
if(type==="html"){const context=encodeURIComponent(JSON.stringify(userContext));url+=`?context=${context}`;}}
return url;}
function getWKHTMLTOPDF_MESSAGES(status){const link='<br><br><a href="http://wkhtmltopdf.org/" target="_blank">wkhtmltopdf.org</a>';const _status={broken:_t("Your installation of Wkhtmltopdf seems to be broken. The report will be shown in html.%(link)s",{link}),install:_t("Unable to find Wkhtmltopdf on this system. The report will be shown in html.%(link)s",{link}),upgrade:_t("You should upgrade your version of Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as support for table-breaking between pages.%(link)s",{link}),workers:_t("You need to start Odoo with at least two workers to print a pdf version of the reports."),};return _status[status];}
__exports.downloadReport=downloadReport;async function downloadReport(rpc,action,type,userContext){let message;if(type==="pdf"){downloadReport.wkhtmltopdfStatusProm||=rpc("/report/check_wkhtmltopdf");const status=await downloadReport.wkhtmltopdfStatusProm;message=getWKHTMLTOPDF_MESSAGES(status);if(!["upgrade","ok"].includes(status)){return{success:false,message};}}
const url=getReportUrl(action,type);await download({url:"/report/download",data:{data:JSON.stringify([url,action.report_type]),context:JSON.stringify(userContext),},});return{success:true,message};}
return __exports;});;

/* /web/static/src/webclient/actions/reports/report_action.js */
odoo.define('@web/webclient/actions/reports/report_action',['@web/core/utils/hooks','@web/search/action_hook','@web/search/layout','@web/views/view','@web/webclient/actions/reports/report_hook','@odoo/owl'],function(require){'use strict';let __exports={};const{useService}=require("@web/core/utils/hooks");const{useSetupAction}=require("@web/search/action_hook");const{Layout}=require("@web/search/layout");const{getDefaultConfig}=require("@web/views/view");const{useEnrichWithActionLinks}=require("@web/webclient/actions/reports/report_hook");const{Component,useRef,useSubEnv}=require("@odoo/owl");const ReportAction=__exports.ReportAction=class ReportAction extends Component{static components={Layout};static template="web.ReportAction";static props=["*"];setup(){useSubEnv({config:{...getDefaultConfig(),...this.env.config,},});useSetupAction();this.action=useService("action");this.title=this.props.display_name||this.props.name;this.reportUrl=this.props.report_url;this.iframe=useRef("iframe");useEnrichWithActionLinks(this.iframe);}
onIframeLoaded(ev){const iframeDocument=ev.target.contentWindow.document;iframeDocument.body.classList.add("o_in_iframe","container-fluid");iframeDocument.body.classList.remove("container");}
print(){this.action.doAction({type:"ir.actions.report",report_type:"qweb-pdf",report_name:this.props.report_name,report_file:this.props.report_file,data:this.props.data||{},context:this.props.context||{},display_name:this.title,});}}
return __exports;});;

/* /web/static/src/webclient/actions/reports/report_hook.js */
odoo.define('@web/webclient/actions/reports/report_hook',['@odoo/owl'],function(require){'use strict';let __exports={};const{useComponent,useEffect}=require("@odoo/owl");__exports.useEnrichWithActionLinks=useEnrichWithActionLinks;function useEnrichWithActionLinks(ref,selector=null){const comp=useComponent();useEffect((element)=>{if(element.matches("iframe")){element.onload=()=>enrich(comp,element,selector,true);}else{enrich(comp,element,selector);}},()=>[ref.el]);}
function enrich(component,targetElement,selector,isIFrame=false){let doc=window.document;if(isIFrame){targetElement=targetElement.contentDocument;doc=targetElement;}
const targets=[];if(selector){targets.push(...targetElement.querySelectorAll(selector));}else{targets.push(targetElement);}
for(const currentTarget of targets){const elementsToWrap=currentTarget.querySelectorAll("[res-id][res-model][view-type]");for(const element of elementsToWrap.values()){const wrapper=doc.createElement("a");wrapper.setAttribute("href","#");wrapper.addEventListener("click",(ev)=>{ev.preventDefault();component.env.services.action.doAction({type:"ir.actions.act_window",view_mode:element.getAttribute("view-type"),res_id:Number(element.getAttribute("res-id")),res_model:element.getAttribute("res-model"),views:[[element.getAttribute("view-id"),element.getAttribute("view-type")]],});});element.parentNode.insertBefore(wrapper,element);wrapper.appendChild(element);}}}
return __exports;});;

/* /web/static/src/views/utils.js */
odoo.define('@web/views/utils',['@web/core/l10n/translation','@web/core/registry','@web/core/utils/strings','@web/model/relational_model/utils'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{exprToBoolean}=require("@web/core/utils/strings");const{combineModifiers}=require("@web/model/relational_model/utils");const X2M_TYPES=__exports.X2M_TYPES=["one2many","many2many"];const NUMERIC_TYPES=["integer","float","monetary"];const BUTTON_CLICK_PARAMS=__exports.BUTTON_CLICK_PARAMS=["name","type","args","block-ui","context","close","cancel-label","confirm","confirm-title","confirm-label","special","effect","help","debounce","noSaveDialog",];function getViewClass(type){const isValidType=Boolean(type)&&registry.category("views").contains(type);return isValidType&&`o_${type}_view`;}
__exports.computeViewClassName=computeViewClassName;function computeViewClassName(viewType,rootNode,additionalClassList=[]){const subType=rootNode?.getAttribute("js_class");const classList=rootNode?.getAttribute("class")?.split(" ")||[];const uniqueClasses=new Set([getViewClass(viewType),getViewClass(subType),...classList,...additionalClassList,]);return Array.from(uniqueClasses).filter((c)=>c).join(" ");}
const computeReportMeasures=__exports.computeReportMeasures=(fields,fieldAttrs,activeMeasures,{sumAggregatorOnly=false}={})=>{const measures={__count:{name:"__count",string:_t("Count"),type:"integer"},};for(const[fieldName,field]of Object.entries(fields)){if(fieldName==="id"){continue;}
const{isInvisible}=fieldAttrs[fieldName]||{};if(isInvisible){continue;}
if(["integer","float","monetary"].includes(field.type)&&((sumAggregatorOnly&&field.aggregator==="sum")||(!sumAggregatorOnly&&field.aggregator))){measures[fieldName]=field;}}
for(const measure of activeMeasures){if(!measures[measure]){measures[measure]=fields[measure];}}
for(const fieldName in fieldAttrs){if(fieldAttrs[fieldName].string&&fieldName in measures){measures[fieldName].string=fieldAttrs[fieldName].string;}}
const sortedMeasures=Object.entries(measures).sort(([m1,f1],[m2,f2])=>{if(m1==="__count"||m2==="__count"){return m1==="__count"?1:-1;}
return f1.string.toLowerCase().localeCompare(f2.string.toLowerCase());});return Object.fromEntries(sortedMeasures);};__exports.getFormattedValue=getFormattedValue;function getFormattedValue(record,fieldName,fieldInfo=null){const field=record.fields[fieldName];const formatter=registry.category("formatters").get(field.type,(val)=>val);const formatOptions={};if(fieldInfo&&formatter.extractOptions){Object.assign(formatOptions,formatter.extractOptions(fieldInfo));}
formatOptions.data=record.data;formatOptions.field=field;return record.data[fieldName]!==undefined?formatter(record.data[fieldName],formatOptions):"";}
__exports.getActiveActions=getActiveActions;function getActiveActions(rootNode){const activeActions={type:"view",edit:exprToBoolean(rootNode.getAttribute("edit"),true),create:exprToBoolean(rootNode.getAttribute("create"),true),delete:exprToBoolean(rootNode.getAttribute("delete"),true),};activeActions.duplicate=activeActions.create&&exprToBoolean(rootNode.getAttribute("duplicate"),true);return activeActions;}
__exports.getClassNameFromDecoration=getClassNameFromDecoration;function getClassNameFromDecoration(decoration){if(decoration==="bf"){return"fw-bold";}else if(decoration==="it"){return"fst-italic";}
return`text-${decoration}`;}
__exports.getDecoration=getDecoration;function getDecoration(rootNode){const decorations=[];for(const name of rootNode.getAttributeNames()){if(name.startsWith("decoration-")){decorations.push({class:getClassNameFromDecoration(name.replace("decoration-","")),condition:rootNode.getAttribute(name),});}}
return decorations;}
__exports.isX2Many=isX2Many;function isX2Many(field){return field&&X2M_TYPES.includes(field.type);}
__exports.isNumeric=isNumeric;function isNumeric(field){return NUMERIC_TYPES.includes(field.type);}
__exports.isNull=isNull;function isNull(value){return[null,undefined].includes(value);}
__exports.processButton=processButton;function processButton(node){const withDefault={close:(val)=>exprToBoolean(val,false),context:(val)=>val||"{}",};const clickParams={};const attrs={};for(const{name,value}of node.attributes){if(BUTTON_CLICK_PARAMS.includes(name)){clickParams[name]=withDefault[name]?withDefault[name](value):value;}else{attrs[name]=value;}}
return{className:node.getAttribute("class")||"",disabled:!!node.getAttribute("disabled")||false,icon:node.getAttribute("icon")||false,title:node.getAttribute("title")||undefined,string:node.getAttribute("string")||undefined,options:JSON.parse(node.getAttribute("options")||"{}"),display:node.getAttribute("display")||"selection",clickParams,column_invisible:node.getAttribute("column_invisible"),invisible:combineModifiers(node.getAttribute("column_invisible"),node.getAttribute("invisible"),"OR"),readonly:node.getAttribute("readonly"),required:node.getAttribute("required"),attrs,};}
__exports.processMeasure=processMeasure;function processMeasure(measure){if(Array.isArray(measure)){return measure.map(processMeasure);}
return measure==="__count__"?"__count":measure;}
__exports.toStringExpression=toStringExpression;function toStringExpression(str){return`\`${str.replaceAll("`", "\\`")}\``;}
__exports.uuid=uuid;function uuid(){const array=new Uint8Array(8);window.crypto.getRandomValues(array);return[...array].map((b)=>b.toString(16).padStart(2,"0")).join("");}
return __exports;});;

/* /web/static/src/views/fields/formatters.js */
odoo.define('@web/views/fields/formatters',['@web/core/l10n/dates','@web/core/l10n/localization','@web/core/l10n/translation','@web/core/registry','@web/core/utils/binary','@web/core/utils/numbers','@web/core/utils/strings','@odoo/owl','@web/core/currency'],function(require){'use strict';let __exports={};const{formatDate:_formatDate,formatDateTime:_formatDateTime}=require("@web/core/l10n/dates");const{localization:l10n}=require("@web/core/l10n/localization");const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{isBinarySize}=require("@web/core/utils/binary");const{formatFloat:formatFloatNumber,humanNumber,insertThousandsSep,}=require("@web/core/utils/numbers");const{escape,exprToBoolean}=require("@web/core/utils/strings");const{markup}=require("@odoo/owl");const{formatCurrency}=require("@web/core/currency");function humanSize(value){if(!value){return"";}
const suffix=value<1024?" "+_t("Bytes"):"b";return(humanNumber(value,{decimals:2,})+suffix);}
__exports.formatBinary=formatBinary;function formatBinary(value){if(!isBinarySize(value)){return humanSize(value.length/1.37);}
return value;}
__exports.formatBoolean=formatBoolean;function formatBoolean(value){return markup(`
        <div class="o-checkbox d-inline-block me-2">
            <input id="boolean_checkbox" type="checkbox" class="form-check-input" disabled ${
                value ? "checked" : ""
            }/>
            <label for="boolean_checkbox" class="form-check-label"/>
        </div>`);}
__exports.formatChar=formatChar;function formatChar(value,options){if(options&&options.isPassword){return"*".repeat(value?value.length:0);}
if(options&&options.escape){value=escape(value);}
return value;}
formatChar.extractOptions=({attrs})=>{return{isPassword:exprToBoolean(attrs.password),};};__exports.formatDate=formatDate;function formatDate(value,options){return _formatDate(value,options);}
formatDate.extractOptions=({options})=>{return{condensed:options.condensed};};__exports.formatDateTime=formatDateTime;function formatDateTime(value,options={}){if(options.showTime===false){return _formatDate(value,options);}
return _formatDateTime(value,options);}
formatDateTime.extractOptions=({attrs,options})=>{return{...formatDate.extractOptions({attrs,options}),showSeconds:exprToBoolean(options.show_seconds??true),showTime:exprToBoolean(options.show_time??true),};};__exports.formatFloat=formatFloat;function formatFloat(value,options={}){if(value===false){return"";}
if(!options.digits&&options.field){options.digits=options.field.digits;}
if(!options.minDigits&&options.field){options.minDigits=options.field.min_display_digits;}
return formatFloatNumber(value,options);}
formatFloat.extractOptions=({attrs,options})=>{let digits;if(attrs.digits){digits=JSON.parse(attrs.digits);}else if(options.digits){digits=options.digits;}
const minDigits=options.minDigits;const humanReadable=!!options.human_readable;const decimals=options.decimals||0;return{decimals,digits,minDigits,humanReadable};};__exports.formatFloatFactor=formatFloatFactor;function formatFloatFactor(value,options={}){if(value===false){return"";}
const factor=options.factor||1;if(!options.digits&&options.field){options.digits=options.field.digits;}
return formatFloatNumber(value*factor,options);}
formatFloatFactor.extractOptions=({attrs,options})=>{return{...formatFloat.extractOptions({attrs,options}),factor:options.factor,};};__exports.formatFloatTime=formatFloatTime;function formatFloatTime(value,options={}){if(value===false){return"";}
const isNegative=value<0;value=Math.abs(value);let hour=Math.floor(value);const milliSecLeft=Math.round(value*3600000)-hour*3600000;let min=milliSecLeft/60000;if(options.displaySeconds){min=Math.floor(min);}else{min=Math.round(min);}
if(min===60){min=0;hour=hour+1;}
min=String(min).padStart(2,"0");if(!options.noLeadingZeroHour){hour=String(hour).padStart(2,"0");}
let sec="";if(options.displaySeconds){sec=":"+String(Math.floor((milliSecLeft%60000)/1000)).padStart(2,"0");}
return`${isNegative ? "-" : ""}${hour}:${min}${sec}`;}
formatFloatTime.extractOptions=({options})=>{return{displaySeconds:options.displaySeconds,};};__exports.formatInteger=formatInteger;function formatInteger(value,options={}){if(value===false||value===null){return"";}
if(options.isPassword){return"*".repeat(value.length);}
if(options.humanReadable){return humanNumber(value,options);}
const grouping=options.grouping||l10n.grouping;const thousandsSep="thousandsSep"in options?options.thousandsSep:l10n.thousandsSep;return insertThousandsSep(value.toFixed(0),thousandsSep,grouping);}
formatInteger.extractOptions=({attrs,options})=>{return{decimals:options.decimals||0,humanReadable:!!options.human_readable,isPassword:exprToBoolean(attrs.password),};};__exports.formatMany2one=formatMany2one;function formatMany2one(value,options){if(!value){value="";}else if(value[1]){value=value[1];}else{value=_t("Unnamed");}
if(options&&options.escape){value=encodeURIComponent(value);}
return value;}
__exports.formatX2many=formatX2many;function formatX2many(value){const count=value.currentIds.length;if(count===0){return _t("No records");}else if(count===1){return _t("1 record");}else{return _t("%s records",count);}}
__exports.formatMonetary=formatMonetary;function formatMonetary(value,options={}){if(value===false){return"";}
let currencyId=options.currencyId;if(!currencyId&&options.data){const currencyField=options.currencyField||(options.field&&options.field.currency_field)||"currency_id";const dataValue=options.data[currencyField];currencyId=Array.isArray(dataValue)?dataValue[0]:dataValue;}
return formatCurrency(value,currencyId,options);}
formatMonetary.extractOptions=({options})=>{return{noSymbol:options.no_symbol,currencyField:options.currency_field,};};__exports.formatPercentage=formatPercentage;function formatPercentage(value,options={}){value=value||0;options=Object.assign({trailingZeros:false,thousandsSep:""},options);if(!options.digits&&options.field){options.digits=options.field.digits;}
const formatted=formatFloatNumber(value*100,options);return`${formatted}${options.noSymbol ? "" : "%"}`;}
formatPercentage.extractOptions=formatFloat.extractOptions;function formatProperties(value,field){if(!value||!value.length){return"";}
return value.map((property)=>property["string"]).join(", ");}
__exports.formatReference=formatReference;function formatReference(value,options){return formatMany2one(value?[value.resId,value.displayName]:false,options);}
__exports.formatMany2oneReference=formatMany2oneReference;function formatMany2oneReference(value){return value?formatMany2one([value.resId,value.displayName]):"";}
__exports.formatSelection=formatSelection;function formatSelection(value,options={}){const selection=options.selection||(options.field&&options.field.selection)||[];const option=selection.find((option)=>option[0]===value);return option?option[1]:"";}
__exports.formatText=formatText;function formatText(value){return value?value.toString():"";}
__exports.formatHtml=formatHtml;function formatHtml(value){return value;}
__exports.formatJson=formatJson;function formatJson(value){return(value&&JSON.stringify(value))||"";}
registry.category("formatters").add("binary",formatBinary).add("boolean",formatBoolean).add("char",formatChar).add("date",formatDate).add("datetime",formatDateTime).add("float",formatFloat).add("float_factor",formatFloatFactor).add("float_time",formatFloatTime).add("html",formatHtml).add("integer",formatInteger).add("json",formatJson).add("many2one",formatMany2one).add("many2one_reference",formatMany2oneReference).add("one2many",formatX2many).add("many2many",formatX2many).add("monetary",formatMonetary).add("percentage",formatPercentage).add("properties",formatProperties).add("properties_definition",formatProperties).add("reference",formatReference).add("selection",formatSelection).add("text",formatText);return __exports;});;

/* /web/static/src/views/fields/file_handler.js */
odoo.define('@web/views/fields/file_handler',['@web/core/l10n/translation','@web/core/utils/hooks','@web/core/utils/urls','@web/core/utils/files','@odoo/owl'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{getDataURLFromFile}=require("@web/core/utils/urls");const{checkFileSize}=require("@web/core/utils/files");const{Component,useRef,useState}=require("@odoo/owl");const FileUploader=__exports.FileUploader=class FileUploader extends Component{static template="web.FileUploader";static props={onClick:{type:Function,optional:true},onUploaded:Function,onUploadComplete:{type:Function,optional:true},multiUpload:{type:Boolean,optional:true},checkSize:{type:Boolean,optional:true},inputName:{type:String,optional:true},fileUploadClass:{type:String,optional:true},acceptedFileExtensions:{type:String,optional:true},slots:{type:Object,optional:true},showUploadingText:{type:Boolean,optional:true},};static defaultProps={checkSize:true,showUploadingText:true,};setup(){this.notification=useService("notification");this.fileInputRef=useRef("fileInput");this.state=useState({isUploading:false,});}
async onFileChange(ev){if(!ev.target.files.length){return;}
const{target}=ev;for(const file of ev.target.files){if(this.props.checkSize&&!checkFileSize(file.size,this.notification)){return null;}
this.state.isUploading=true;const data=await getDataURLFromFile(file);if(!file.size){console.warn(`Error while uploading file : ${file.name}`);this.notification.add(_t("There was a problem while uploading your file."),{type:"danger",});}
try{await this.props.onUploaded({name:file.name,size:file.size,type:file.type,data:data.split(",")[1],objectUrl:file.type==="application/pdf"?URL.createObjectURL(file):null,});}finally{this.state.isUploading=false;}}
target.value=null;if(this.props.multiUpload&&this.props.onUploadComplete){this.props.onUploadComplete({});}}
async onSelectFileButtonClick(ev){if(this.props.onClick){const ok=await this.props.onClick(ev);if(ok!==undefined&&!ok){return;}}
this.fileInputRef.el.click();}}
return __exports;});;

/* /mail/static/src/model/export.js */
odoo.define('@mail/model/export',['@mail/model/store','@mail/model/record','@mail/model/make_store','@mail/model/misc'],function(require){'use strict';let __exports={};Object.assign(__exports,require("@mail/model/store"));Object.assign(__exports,require("@mail/model/record"));Object.assign(__exports,require("@mail/model/make_store"));{const{AND,OR}=require("@mail/model/misc");Object.assign(__exports,{AND,OR})};return __exports;});;

/* /mail/static/src/model/make_store.js */
odoo.define('@mail/model/make_store',['@odoo/owl','@mail/model/store','@mail/model/misc','@mail/model/record','@mail/model/store_internal','@mail/model/model_internal','@mail/model/record_internal'],function(require){'use strict';let __exports={};const{markRaw,reactive,toRaw}=require("@odoo/owl");const{Store}=require("@mail/model/store");const{STORE_SYM,isFieldDefinition,isMany,isRelation,modelRegistry}=require("@mail/model/misc");const{Record}=require("@mail/model/record");const{StoreInternal}=require("@mail/model/store_internal");const{ModelInternal}=require("@mail/model/model_internal");const{RecordInternal}=require("@mail/model/record_internal");__exports.makeStore=makeStore;function makeStore(env,{localRegistry}={}){const recordByLocalId=reactive(new Map());Store.env=env;let store=new Store();store.env=env;store.Model=Store;store._=markRaw(new StoreInternal());store._raw=store;store._proxyInternal=store;store._proxy=store;store.recordByLocalId=recordByLocalId;Record.store=store;const Models={};const chosenModelRegistry=localRegistry??modelRegistry;for(const[,_OgClass]of chosenModelRegistry.getEntries()){const OgClass=_OgClass;if(store[OgClass.getName()]){throw new Error(`There must be no duplicated Model Names (duplicate found: ${OgClass.getName()})`);}
const Model=Object.create(OgClass);const Class={[OgClass.getName()]:class extends OgClass{constructor(){super();this.setup();const record=this;record._raw=record;record.Model=Model;record._=markRaw(record[STORE_SYM]?new StoreInternal():new RecordInternal());const recordProxyInternal=new Proxy(record,{get(record,name,recordFullProxy){recordFullProxy=record._.downgradeProxy(record,recordFullProxy);if(record._.gettingField||!Model._.fields.get(name)){let res=Reflect.get(...arguments);if(typeof res==="function"){res=res.bind(recordFullProxy);}
return res;}
if(Model._.fieldsCompute.get(name)&&!Model._.fieldsEager.get(name)){record._.fieldsComputeInNeed.set(name,true);if(record._.fieldsComputeOnNeed.get(name)){record._.compute(record,name);}}
if(Model._.fieldsSort.get(name)&&!Model._.fieldsEager.get(name)){record._.fieldsSortInNeed.set(name,true);if(record._.fieldsSortOnNeed.get(name)){record._.sort(record,name);}}
record._.gettingField=true;const val=recordFullProxy[name];record._.gettingField=false;if(isRelation(Model,name)){const recordListFullProxy=val._proxy;if(isMany(Model,name)){return recordListFullProxy;}
return recordListFullProxy[0];}
return Reflect.get(record,name,recordFullProxy);},deleteProperty(record,name){return store.MAKE_UPDATE(function recordDeleteProperty(){if(isRelation(Model,name)){const recordList=record[name];recordList.clear();return true;}
return Reflect.deleteProperty(record,name);});},set(record,name,val,receiver){if(record._.updatingAttrs.has(name)){record[name]=val;return true;}
return store.MAKE_UPDATE(function recordSet(){const reactiveSet=receiver!==record._proxyInternal;if(reactiveSet){record._.proxyUsed.set(name,true);}
store._.updateFields(record,{[name]:val});if(reactiveSet){record._.proxyUsed.delete(name);}
return true;});},});record._proxyInternal=recordProxyInternal;const recordProxy=reactive(recordProxyInternal);record._proxy=recordProxy;if(record?.[STORE_SYM]){record.recordByLocalId=store.recordByLocalId;record._=markRaw(toRaw(store._));store=record;Record.store=store;}
for(const name of Model._.fields.keys()){record._.prepareField(record,name,recordProxy);}
return recordProxy;}},}[OgClass.getName()];Model._=markRaw(new ModelInternal());Object.assign(Model,{Class,env,records:reactive({}),});Models[Model.getName()]=Model;store[Model.getName()]=Model;const obj=new OgClass();obj.setup();for(const[name,val]of Object.entries(obj)){if(isFieldDefinition(val)){Model._.prepareField(name,val);}}}
for(const Model of Object.values(Models)){for(const name of Model._.fields.keys()){if(!isRelation(Model,name)){continue;}
const targetModel=Model._.fieldsTargetModel.get(name);const inverse=Model._.fieldsInverse.get(name);if(targetModel&&!Models[targetModel]){throw new Error(`No target model ${targetModel} exists`);}
if(inverse){const OtherModel=Models[targetModel];const rel2TargetModel=OtherModel._.fieldsTargetModel.get(inverse);const rel2Inverse=OtherModel._.fieldsInverse.get(inverse);if(rel2TargetModel&&rel2TargetModel!==Model.getName()){throw new Error(`Fields ${Models[
                            targetModel
                        ].getName()}.${inverse} has wrong targetModel. Expected: "${Model.getName()}" Actual: "${rel2TargetModel}"`);}
if(rel2Inverse&&rel2Inverse!==name){throw new Error(`Fields ${Models[
                            targetModel
                        ].getName()}.${inverse} has wrong inverse. Expected: "${name}" Actual: "${rel2Inverse}"`);}
OtherModel._.fieldsTargetModel.set(inverse,Model.getName());OtherModel._.fieldsInverse.set(inverse,name);Model._.fieldsEager.set(name,true);OtherModel._.fieldsEager.set(inverse,true);}}}
Object.assign(store.Store,{store,_rawStore:store});store=toRaw(store.Store.insert())._raw;for(const Model of Object.values(Models)){Model._rawStore=store;Model.store=store._proxy;store._proxy[Model.getName()]=Model;}
Object.assign(store,{Models,storeReady:true});return store._proxy;}
return __exports;});;

/* /mail/static/src/model/misc.js */
odoo.define('@mail/model/misc',['@odoo/owl','@web/core/registry'],function(require){'use strict';let __exports={};const{markup}=require("@odoo/owl");const{registry}=require("@web/core/registry");const modelRegistry=__exports.modelRegistry=registry.category("discuss.model");const Markup=__exports.Markup=markup("").constructor;const FIELD_DEFINITION_SYM=__exports.FIELD_DEFINITION_SYM=Symbol("field_definition");const ATTR_SYM=__exports.ATTR_SYM=Symbol("attr");const MANY_SYM=__exports.MANY_SYM=Symbol("many");const ONE_SYM=__exports.ONE_SYM=Symbol("one");const OR_SYM=__exports.OR_SYM=Symbol("or");const AND_SYM=Symbol("and");const IS_RECORD_SYM=__exports.IS_RECORD_SYM=Symbol("isRecord");const IS_FIELD_SYM=__exports.IS_FIELD_SYM=Symbol("isField");const IS_DELETING_SYM=__exports.IS_DELETING_SYM=Symbol("isDeleting");const IS_DELETED_SYM=__exports.IS_DELETED_SYM=Symbol("isDeleted");const STORE_SYM=__exports.STORE_SYM=Symbol("store");__exports.AND=AND;function AND(...args){return[AND_SYM,...args];}
__exports.OR=OR;function OR(...args){return[OR_SYM,...args];}
__exports.isCommand=isCommand;function isCommand(data){return["ADD","DELETE","ADD.noinv","DELETE.noinv"].includes(data?.[0]?.[0]);}
__exports.isOne=isOne;function isOne(Model,fieldName){return Model._.fieldsOne.get(fieldName);}
__exports.isMany=isMany;function isMany(Model,fieldName){return Model._.fieldsMany.get(fieldName);}
__exports.isRecord=isRecord;function isRecord(record){return Boolean(record?._?.[IS_RECORD_SYM]);}
__exports.isRelation=isRelation;function isRelation(Model,fieldName){return isMany(Model,fieldName)||isOne(Model,fieldName);}
__exports.isFieldDefinition=isFieldDefinition;function isFieldDefinition(val){return val?.[FIELD_DEFINITION_SYM];}
return __exports;});;

/* /mail/static/src/model/model_internal.js */
odoo.define('@mail/model/model_internal',['@mail/model/misc'],function(require){'use strict';let __exports={};const{ATTR_SYM,MANY_SYM,ONE_SYM}=require("@mail/model/misc");const ModelInternal=__exports.ModelInternal=class ModelInternal{fields=new Map();fieldsAttr=new Map();fieldsOne=new Map();fieldsMany=new Map();fieldsHtml=new Map();fieldsTargetModel=new Map();fieldsCompute=new Map();fieldsEager=new Map();fieldsInverse=new Map();fieldsOnAdd=new Map();fieldsOnDelete=new Map();fieldsOnUpdate=new Map();fieldsSort=new Map();fieldsType=new Map();prepareField(fieldName,data){this.fields.set(fieldName,true);if(data[ATTR_SYM]){this.fieldsAttr.set(fieldName,true);}
if(data[ONE_SYM]){this.fieldsOne.set(fieldName,true);}
if(data[MANY_SYM]){this.fieldsMany.set(fieldName,true);}
for(const key in data){const value=data[key];switch(key){case"html":{if(!value){break;}
this.fieldsHtml.set(fieldName,value);break;}
case"targetModel":{this.fieldsTargetModel.set(fieldName,value);break;}
case"compute":{this.fieldsCompute.set(fieldName,value);break;}
case"eager":{if(!value){break;}
this.fieldsEager.set(fieldName,value);break;}
case"sort":{this.fieldsSort.set(fieldName,value);break;}
case"inverse":{this.fieldsInverse.set(fieldName,value);break;}
case"onAdd":{this.fieldsOnAdd.set(fieldName,value);break;}
case"onDelete":{this.fieldsOnDelete.set(fieldName,value);break;}
case"onUpdate":{this.fieldsOnUpdate.set(fieldName,value);break;}
case"type":{this.fieldsType.set(fieldName,value);break;}}}}}
return __exports;});;

/* /mail/static/src/model/record.js */
odoo.define('@mail/model/record',['@odoo/owl','@mail/model/misc','@web/core/l10n/dates'],function(require){'use strict';let __exports={};const{toRaw}=require("@odoo/owl");const{ATTR_SYM,FIELD_DEFINITION_SYM,IS_DELETED_SYM,MANY_SYM,ONE_SYM,OR_SYM,isCommand,isMany,isOne,isRecord,isRelation,modelRegistry,}=require("@mail/model/misc");const{serializeDate,serializeDateTime}=require("@web/core/l10n/dates");const Record=__exports.Record=class Record{static _;_;static id;static env;env;static records;static store;static MAKE_UPDATE(fn){return this.store.MAKE_UPDATE(...arguments);}
static onChange(record,name,cb){return this.store.onChange(...arguments);}
static get(data){const Model=toRaw(this);return this.records[Model.localId(data)];}
static getName(){return this._name||this.name;}
static register(localRegistry){if(localRegistry){localRegistry.add(this.getName(),this);}else{modelRegistry.add(this.getName(),this);}}
static localId(data){const Model=toRaw(this);let idStr;if(typeof data==="object"&&data!==null){idStr=Model._localId(Model.id,data);}else{idStr=data;}
return`${Model.getName()},${idStr}`;}
static _localId(expr,data,{brackets=false}={}){const Model=toRaw(this);if(!Array.isArray(expr)){if(Model._.fields.get(expr)){if(Model._.fieldsMany.get(expr)){throw new Error("Using a Record.Many() as id is not (yet) supported");}
if(!isRelation(Model,expr)){return data[expr];}
if(isCommand(data[expr])){const[cmd,data2]=data[expr].at(-1);if(cmd==="DELETE"){return undefined;}else{return`(${data2?.localId})`;}}
if(isRecord(data[expr])){return`(${data[expr]?.localId})`;}
const TargetModelName=Model._.fieldsTargetModel.get(expr);return`(${Model.store[TargetModelName].get(data[expr])?.localId})`;}
return data[expr];}
const vals=[];for(let i=1;i<expr.length;i++){vals.push(Model._localId(expr[i],data,{brackets:true}));}
let res=vals.join(expr[0]===OR_SYM?" OR ":" AND ");if(brackets){res=`(${res})`;}
return res;}
static _retrieveIdFromData(data){const Model=toRaw(this);const res={};function _deepRetrieve(expr2){if(typeof expr2==="string"){if(isCommand(data[expr2])){const[cmd,data2]=data[expr2].at(-1);return Object.assign(res,{[expr2]:cmd==="DELETE"?undefined:cmd==="DELETE.noinv"?[["DELETE.noinv",data2]]:cmd==="ADD.noinv"?[["ADD.noinv",data2]]:data2,});}
return Object.assign(res,{[expr2]:data[expr2]});}
if(expr2 instanceof Array){for(const expr of this.id){if(typeof expr==="symbol"){continue;}
_deepRetrieve(expr);}}}
if(Model.id===undefined){return res;}
if(typeof Model.id==="string"){if(typeof data!=="object"||data===null){return{[Model.id]:data};}
if(isCommand(data[Model.id])){const[cmd,data2]=data[Model.id].at(-1);return Object.assign(res,{[Model.id]:cmd==="DELETE"?undefined:cmd==="DELETE.noinv"?[["DELETE.noinv",data2]]:cmd==="ADD.noinv"?[["ADD.noinv",data2]]:data2,});}
return{[Model.id]:data[Model.id]};}
for(const expr of Model.id){if(typeof expr==="symbol"){continue;}
_deepRetrieve(expr);}
return res;}
static Class;static new(data,ids){const Model=toRaw(this);const store=Model._rawStore;return store.MAKE_UPDATE(function RecordNew(){const recordProxy=new Model.Class();const record=toRaw(recordProxy)._raw;Object.assign(record._,{localId:Model.localId(ids)});Object.assign(recordProxy,{...ids});Model.records[record.localId]=recordProxy;if(record.Model.getName()==="Store"){Object.assign(record,{env:Model._rawStore.env,recordByLocalId:Model._rawStore.recordByLocalId,});}
Model._rawStore.recordByLocalId.set(record.localId,recordProxy);for(const fieldName of record.Model._.fields.keys()){record._.requestCompute?.(record,fieldName);record._.requestSort?.(record,fieldName);}
return recordProxy;});}
static one(targetModel,param1){return{...param1,targetModel,[FIELD_DEFINITION_SYM]:true,[ONE_SYM]:true};}
static many(targetModel,param1){return{...param1,targetModel,[FIELD_DEFINITION_SYM]:true,[MANY_SYM]:true};}
static attr(def,param1){return{...param1,[FIELD_DEFINITION_SYM]:true,[ATTR_SYM]:true,default:def};}
static insert(data,options={}){const ModelFullProxy=this;const Model=toRaw(ModelFullProxy);const store=Model._rawStore;return store.MAKE_UPDATE(function RecordInsert(){const isMulti=Array.isArray(data);if(!isMulti){data=[data];}
const oldTrusted=store._.trusted;store._.trusted=options.html??store._.trusted;const res=data.map(function RecordInsertMap(d){return Model._insert.call(ModelFullProxy,d,options);});store._.trusted=oldTrusted;if(!isMulti){return res[0];}
return res;});}
static _insert(data){const ModelFullProxy=this;const Model=toRaw(ModelFullProxy);const recordFullProxy=Model.preinsert.call(ModelFullProxy,data);const record=toRaw(recordFullProxy)._raw;record.update.call(record._proxy,data);return recordFullProxy;}
static preinsert(data){const ModelFullProxy=this;const Model=toRaw(ModelFullProxy);const ids=Model._retrieveIdFromData(data);for(const name in ids){if(ids[name]&&!isRecord(ids[name])&&!isCommand(ids[name])&&isRelation(Model,name)){ids[name]=Model._rawStore[Model._.fieldsTargetModel.get(name)].preinsert(ids[name]);}}
return Model.get.call(ModelFullProxy,data)??Model.new(data,ids);}
get store(){return toRaw(this)._raw.Model._rawStore._proxy;}
get _rawStore(){return toRaw(this)._raw.Model._rawStore;}
Model;get localId(){return toRaw(this)._.localId;}
_raw;_proxyInternal;_proxy;setup(){}
update(data){const record=toRaw(this)._raw;const store=record._rawStore;return store.MAKE_UPDATE(function recordUpdate(){if(typeof data==="object"&&data!==null){store._.updateFields(record,data);}else{store._.updateFields(record,{[record.Model.id]:data});}});}
delete(){const record=toRaw(this)._raw;const store=record._rawStore;return store.MAKE_UPDATE(function recordDelete(){store._.ADD_QUEUE("delete",record);});}
exists(){return!this[IS_DELETED_SYM];}
eq(record){return toRaw(this)._raw===toRaw(record)?._raw;}
notEq(record){return!this.eq(record);}
in(collection){if(!collection){return false;}
return collection.some((record)=>toRaw(record)._raw.eq(this));}
notIn(collection){return!this.in(collection);}
toData(){const recordProxy=this;const record=toRaw(recordProxy)._raw;const Model=record.Model;const data={...recordProxy};for(const name of Model._.fields.keys()){if(isMany(Model,name)){data[name]=record._proxyInternal[name].map((recordProxy)=>{const record=toRaw(recordProxy)._raw;return record.toIdData.call(record._proxyInternal);});}else if(isOne(Model,name)){const otherRecord=toRaw(record._proxyInternal[name])?._raw;data[name]=otherRecord?.toIdData.call(otherRecord._proxyInternal);}else{const value=recordProxy[name];if(Model._.fieldsType.get(name)==="datetime"&&value){data[name]=serializeDateTime(value);}else if(Model._.fieldsType.get(name)==="date"&&value){data[name]=serializeDate(value);}else{data[name]=value;}}}
delete data._;delete data._fieldsValue;delete data._proxy;delete data._proxyInternal;delete data._raw;delete data.Model;return data;}
toIdData(){const data=this.Model._retrieveIdFromData(this);for(const[name,val]of Object.entries(data)){if(isRecord(val)){data[name]=val.toIdData();}}
return data;}}
Record.register();return __exports;});;

/* /mail/static/src/model/record_internal.js */
odoo.define('@mail/model/record_internal',['@mail/utils/common/misc','@mail/model/misc','@mail/model/record_list','@odoo/owl','@mail/model/record_uses'],function(require){'use strict';let __exports={};const{onChange}=require("@mail/utils/common/misc");const{IS_DELETED_SYM,IS_RECORD_SYM,isRelation}=require("@mail/model/misc");const{RecordList}=require("@mail/model/record_list");const{reactive,toRaw}=require("@odoo/owl");const{RecordUses}=require("@mail/model/record_uses");const RecordInternal=__exports.RecordInternal=class RecordInternal{[IS_RECORD_SYM]=true;fieldsComputing=new Map();fieldsSortOnNeed=new Map();fieldsSortInNeed=new Map();fieldsSorting=new Map();fieldsComputeInNeed=new Map();fieldsComputeOnNeed=new Map();fieldsOnUpdateObserves=new Map();fieldsSortProxy2=new Map();fieldsComputeProxy2=new Map();uses=new RecordUses();updatingAttrs=new Map();proxyUsed=new Map();localId;gettingField=false;prepareField(record,fieldName,recordProxy){const self=this;const Model=toRaw(record).Model;if(isRelation(Model,fieldName)){const recordList=new RecordList();Object.assign(recordList._,{name:fieldName,owner:record,});Object.assign(recordList,{_raw:recordList,_store:record.store,});record[fieldName]=recordList;}else{record[fieldName]=record[fieldName].default;}
if(Model._.fieldsCompute.get(fieldName)){if(!Model._.fieldsEager.get(fieldName)){onChange(recordProxy,fieldName,()=>{if(this.fieldsComputing.get(fieldName)){this.fieldsComputeInNeed.delete(fieldName);}});this.fieldsComputeInNeed.delete(fieldName);this.fieldsSortInNeed.delete(fieldName);}
const cb=function computeObserver(){self.requestCompute(record,fieldName);};const computeProxy2=reactive(recordProxy,cb);this.fieldsComputeProxy2.set(fieldName,computeProxy2);}
if(Model._.fieldsSort.get(fieldName)){if(!Model._.fieldsEager.get(fieldName)){onChange(recordProxy,fieldName,()=>{if(this.fieldsSorting.get(fieldName)){this.fieldsSortInNeed.delete(fieldName);}});this.fieldsComputeInNeed.delete(fieldName);this.fieldsSortInNeed.delete(fieldName);}
const sortProxy2=reactive(recordProxy,function sortObserver(){self.requestSort(record,fieldName);});this.fieldsSortProxy2.set(fieldName,sortProxy2);}
if(Model._.fieldsOnUpdate.get(fieldName)){const store=Model.store;store._onChange(recordProxy,fieldName,(obs)=>{this.fieldsOnUpdateObserves.set(fieldName,obs);if(store._.UPDATE!==0){store._.ADD_QUEUE("onUpdate",record,fieldName);}else{this.onUpdate(record,fieldName);}});}}
requestCompute(record,fieldName,{force=false}={}){if(record[IS_DELETED_SYM]){return;}
const Model=record.Model;if(!Model._.fieldsCompute.get(fieldName)){return;}
const store=record._rawStore;if(store._.UPDATE!==0&&!force){store._.ADD_QUEUE("compute",record,fieldName);}else{if(Model._.fieldsEager.get(fieldName)||this.fieldsComputeInNeed.get(fieldName)){this.compute(record,fieldName);}else{this.fieldsComputeOnNeed.set(fieldName,true);}}}
requestSort(record,fieldName,{force}={}){if(record[IS_DELETED_SYM]){return;}
const Model=record.Model;if(!Model._.fieldsSort.get(fieldName)){return;}
const store=record._rawStore;if(store._.UPDATE!==0&&!force){store._.ADD_QUEUE("sort",record,fieldName);}else{if(Model._.fieldsEager.get(fieldName)||this.fieldsSortInNeed.get(fieldName)){this.sort(record,fieldName);}else{this.fieldsSortOnNeed.set(fieldName,true);}}}
compute(record,fieldName){const Model=record.Model;const store=record._rawStore;this.fieldsComputing.set(fieldName,true);this.fieldsComputeOnNeed.delete(fieldName);store._.updateFields(record,{[fieldName]:Model._.fieldsCompute.get(fieldName).call(this.fieldsComputeProxy2.get(fieldName)),});this.fieldsComputing.delete(fieldName);}
sort(record,fieldName){const Model=record.Model;if(!Model._.fieldsSort.get(fieldName)){return;}
const store=record._rawStore;this.fieldsSortOnNeed.delete(fieldName);this.fieldsSorting.set(fieldName,true);const proxy2Sort=this.fieldsSortProxy2.get(fieldName);const func=Model._.fieldsSort.get(fieldName).bind(proxy2Sort);if(isRelation(Model,fieldName)){store._.sortRecordList(proxy2Sort[fieldName]._proxy,func);}else{const copy=[...proxy2Sort[fieldName]];copy.sort(func);const hasChanged=copy.some((item,index)=>item!==record[fieldName][index]);if(hasChanged){proxy2Sort[fieldName]=copy;}}
this.fieldsSorting.delete(fieldName);}
onUpdate(record,fieldName){const Model=record.Model;if(!Model._.fieldsOnUpdate.get(fieldName)){return;}
Model._.fieldsOnUpdate.get(fieldName).call(record._proxyInternal);this.fieldsOnUpdateObserves.get(fieldName)?.();}
downgradeProxy(record,fullProxy){return record._proxy===fullProxy?record._proxyInternal:fullProxy;}}
return __exports;});;

/* /mail/static/src/model/record_list.js */
odoo.define('@mail/model/record_list',['@odoo/owl','@mail/model/misc'],function(require){'use strict';let __exports={};const{markRaw,reactive,toRaw}=require("@odoo/owl");const{isRecord}=require("@mail/model/misc");function getInverse(reclist){return reclist._.owner.Model._.fieldsInverse.get(reclist._.name);}
function getTargetModel(reclist){return reclist._.owner.Model._.fieldsTargetModel.get(reclist._.name);}
function isComputeField(reclist){return reclist._.owner.Model._.fieldsCompute.get(reclist._.name);}
function isSortField(reclist){return reclist._.owner.Model._.fieldsSort.get(reclist._.name);}
function isEager(reclist){return reclist._.owner.Model._.fieldsEager.get(reclist._.name);}
function setComputeInNeed(reclist){reclist._.owner._.fieldsComputeInNeed.set(reclist._.name,true);}
function setSortInNeed(reclist){reclist._.owner._.fieldsSortInNeed.set(reclist._.name,true);}
function isComputeOnNeed(reclist){return reclist._.owner._.fieldsComputeOnNeed.get(reclist._.name);}
function isSortOnNeed(reclist){return reclist._.owner._.fieldsSortOnNeed.get(reclist._.name);}
function computeField(reclist){reclist._.owner._.compute(reclist._.owner,reclist._.name);}
function sortField(reclist){reclist._.owner._.sort(reclist._.owner,reclist._.name);}
function isOne(reclist){return reclist._.owner.Model._.fieldsOne.get(reclist._.name);}
const RecordListInternal=__exports.RecordListInternal=class RecordListInternal{name;owner;addNoinv(recordList,...records){const self=this;const store=recordList._store;if(isOne(recordList)){const last=records.at(-1);if(isRecord(last)&&last.in(recordList)){return;}
const record=self.insert(recordList,last,function recordList_AddNoInvOneInsert(record){if(record.localId!==recordList.data[0]){const old=recordList._proxy.at(-1);recordList._proxy.data.pop();old?._.uses.delete(recordList);recordList._proxy.data.push(record.localId);self.syncLength(recordList);record._.uses.add(recordList);}},{inv:false});store._.ADD_QUEUE("onAdd",self.owner,self.name,record);return;}
for(const val of records){if(isRecord(val)&&val.in(recordList)){continue;}
const record=self.insert(recordList,val,function recordList_AddNoInvManyInsert(record){if(recordList.data.indexOf(record.localId)===-1){recordList._proxy.data.push(record.localId);self.syncLength(recordList);record._.uses.add(recordList);}},{inv:false});store._.ADD_QUEUE("onAdd",self.owner,self.name,record);}}
assign(recordList,data){const self=this;const store=recordList._store;return store.MAKE_UPDATE(function recordListAssign(){const collection=isRecord(data)?[data]:data;const vals=[...collection];const oldRecords=recordList._proxyInternal.slice.call(recordList._proxy).map((recordProxy)=>toRaw(recordProxy)._raw);const newRecords=vals.map((val)=>self.insert(recordList,val,function recordListAssignInsert(record){if(record.notIn(oldRecords)){record._.uses.add(recordList);store._.ADD_QUEUE("onAdd",self.owner,self.name,record);}}));const inverse=getInverse(recordList);for(const oldRecord of oldRecords){if(oldRecord.notIn(newRecords)){oldRecord._.uses.delete(recordList);store._.ADD_QUEUE("onDelete",self.owner,self.name,oldRecord);if(inverse){oldRecord[inverse].delete(self.owner);}}}
recordList._proxy.data=newRecords.map((newRecord)=>newRecord.localId);recordList._.syncLength(recordList);});}
deleteNoinv(recordList,...records){const self=this;const store=recordList._store;for(const val of records){const record=this.insert(recordList,val,function recordList_DeleteNoInv_Insert(record){const index=recordList.data.indexOf(record.localId);if(index!==-1){recordList.splice.call(recordList._proxy,index,1);self.syncLength(recordList);}},{inv:false});store._.ADD_QUEUE("onDelete",self.owner,self.name,record);}}
downgradeProxy(recordList,fullProxy){return recordList._proxy===fullProxy?recordList._proxyInternal:fullProxy;}
insert(recordList,val,fn,{inv=true,mode="ADD"}={}){const inverse=getInverse(recordList);const targetModel=getTargetModel(recordList);if(typeof val!=="object"){val={[recordList._store[targetModel].id]:val};}
if(inverse&&inv){const target=isRecord(val)&&val._raw===val?val._proxy:val;target[inverse]=[[mode==="ADD"?"ADD.noinv":"DELETE.noinv",recordList._.owner]];}
let newRecordProxy;if(!isRecord(val)){newRecordProxy=recordList._store[targetModel].preinsert(val);}else{newRecordProxy=val;}
const newRecord=toRaw(newRecordProxy)._raw;fn?.(newRecord);if(!isRecord(val)){recordList._store[targetModel].insert(val);}
return newRecord;}
syncLength(reclist){reclist.length=reclist.data.length;}}
const RecordList=__exports.RecordList=class RecordList extends Array{_store;data=[];_raw;_proxyInternal;_proxy;_=markRaw(new RecordListInternal());constructor(){super();const recordList=this;recordList._raw=recordList;const recordListProxyInternal=new Proxy(recordList,{get(recordList,name,recordListFullProxy){recordListFullProxy=recordList._.downgradeProxy(recordList,recordListFullProxy);if(typeof name==="symbol"||Object.keys(recordList).includes(name)||Object.prototype.hasOwnProperty.call(recordList.constructor.prototype,name)){let res=Reflect.get(...arguments);if(typeof res==="function"){res=res.bind(recordListFullProxy);}
return res;}
if(isComputeField(recordList)&&!isEager(recordList)){setComputeInNeed(recordList);if(isComputeOnNeed(recordList)){computeField(recordList);}}
if(name==="length"){return recordListFullProxy.data.length;}
if(isSortField(recordList)&&!isEager(recordList)){setSortInNeed(recordList);if(isSortOnNeed(recordList)){sortField(recordList);}}
if(typeof name!=="symbol"&&!window.isNaN(parseInt(name))){const index=parseInt(name);return recordListFullProxy._store.recordByLocalId.get(recordListFullProxy.data[index]);}
const array=[...recordList[Symbol.iterator].call(recordListFullProxy)];return array[name]?.bind(array);},set(recordList,name,val,recordListProxy){const store=recordList._store;return store.MAKE_UPDATE(function recordListSet(){if(typeof name!=="symbol"&&!window.isNaN(parseInt(name))){const index=parseInt(name);recordList._.insert(recordList,val,function recordListSet_Insert(newRecord){const oldRecord=toRaw(toRaw(recordList._store.recordByLocalId).get(recordList.data[index]))._raw;if(oldRecord&&oldRecord.notEq(newRecord)){oldRecord._.uses.delete(recordList);}
store._.ADD_QUEUE("onDelete",recordList._.owner,recordList._.name,oldRecord);const inverse=getInverse(recordList);if(inverse){oldRecord[inverse].delete(recordList._.owner);}
recordListProxy.data[index]=newRecord?.localId;if(newRecord){newRecord._.uses.add(recordList);store._.ADD_QUEUE("onAdd",recordList._.owner,recordList._.name,newRecord);if(inverse){newRecord[inverse].add(recordList._.owner);}}});}else if(name==="length"){const newLength=parseInt(val);if(newLength!==recordList.data.length){if(newLength<recordList.data.length){recordList.splice.call(recordListProxy,newLength,recordList.length-newLength);}
recordListProxy.data.length=newLength;recordList._.syncLength(recordList);}}else{return Reflect.set(recordList,name,val,recordListProxy);}
return true;});},});recordList._proxyInternal=recordListProxyInternal;recordList._proxy=reactive(recordListProxyInternal);return recordList;}
push(...records){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);const store=recordList._store;return store.MAKE_UPDATE(function recordListPush(){for(const val of records){const record=recordList._.insert(recordList,val,function recordListPushInsert(record){recordList._proxy.data.push(record.localId);recordList._.syncLength(recordList);record._.uses.add(recordList);});store._.ADD_QUEUE("onAdd",recordList._.owner,recordList._.name,record);const inverse=getInverse(recordList);if(inverse){record[inverse].add(recordList._.owner);}}
return recordListFullProxy.data.length;});}
pop(){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);const store=recordList._store;return store.MAKE_UPDATE(function recordListPop(){const oldRecordProxy=recordListFullProxy.at(-1);if(oldRecordProxy){recordList.splice.call(recordListFullProxy,recordListFullProxy.length-1,1);}
return oldRecordProxy;});}
shift(){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);const store=recordList._store;return store.MAKE_UPDATE(function recordListShift(){const recordProxy=recordListFullProxy._store.recordByLocalId.get(recordListFullProxy.data.shift());recordList._.syncLength(recordList);if(!recordProxy){return;}
const record=toRaw(recordProxy)._raw;record._.uses.delete(recordList);store._.ADD_QUEUE("onDelete",recordList._.owner,recordList._.name,record);const inverse=getInverse(recordList);if(inverse){record[inverse].delete(recordList._.owner);}
return recordProxy;});}
unshift(...records){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);const store=recordList._store;return store.MAKE_UPDATE(function recordListUnshift(){for(let i=records.length-1;i>=0;i--){const record=recordList._.insert(recordList,records[i],(record)=>{recordList._proxy.data.unshift(record.localId);recordList._.syncLength(recordList);record._.uses.add(recordList);});store._.ADD_QUEUE("onAdd",recordList._.owner,recordList._.name,record);const inverse=getInverse(recordList);if(inverse){record[inverse].add(recordList._.owner);}}
return recordListFullProxy.data.length;});}
indexOf(recordProxy){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);return recordListFullProxy.data.indexOf(toRaw(recordProxy)?._raw.localId);}
splice(start,deleteCount,...newRecordsProxy){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);const store=recordList._store;return store.MAKE_UPDATE(function recordListSplice(){const oldRecordLocalIds=recordList.data.slice(start,start+deleteCount);const oldRecords=oldRecordLocalIds.map((localId)=>toRaw(toRaw(recordList._store.recordByLocalId).get(localId))._raw);const list=recordListFullProxy.data.slice();list.splice(start,deleteCount,...newRecordsProxy.map((newRecordProxy)=>toRaw(newRecordProxy)._raw.localId));if(isOne(recordList)&&start===0&&deleteCount===1){if(list.length===0){recordList._proxy.data.pop();}else{recordList._proxy.data[0]=list[0];}}else{recordList._proxy.data=list;}
recordList._.syncLength(recordList);for(const oldRecord of oldRecords){oldRecord._.uses.delete(recordList);store._.ADD_QUEUE("onDelete",recordList._.owner,recordList._.name,oldRecord);const inverse=getInverse(recordList);if(inverse){oldRecord[inverse].delete(recordList._.owner);}}
for(const newRecordProxy of newRecordsProxy){const newRecord=toRaw(newRecordProxy)._raw;newRecord._.uses.add(recordList);store._.ADD_QUEUE("onAdd",recordList._.owner,recordList._.name,newRecord);const inverse=getInverse(recordList);if(inverse){newRecord[inverse].add(recordList._.owner);}}});}
sort(func){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);const store=recordList._store;return store.MAKE_UPDATE(function recordListSort(){recordList._store._.sortRecordList(recordListFullProxy,func);return recordListFullProxy;});}
concat(...collections){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);return recordListFullProxy.data.map((localId)=>recordListFullProxy._store.recordByLocalId.get(localId)).concat(...collections.map((c)=>[...c]));}
add(...records){const recordList=toRaw(this)._raw;const store=recordList._store;return store.MAKE_UPDATE(function recordListAdd(){if(isOne(recordList)){const last=records.at(-1);if(isRecord(last)&&recordList.data.includes(toRaw(last)._raw.localId)){return last;}
return recordList._.insert(recordList,last,function recordListAddInsertOne(record){if(record.localId!==recordList.data[0]){recordList.splice.call(recordList._proxy,0,1,record);}});}
const res=[];for(const val of records){if(isRecord(val)&&recordList.data.includes(val.localId)){continue;}
const rec=recordList._.insert(recordList,val,function recordListAddInsertMany(record){if(recordList.data.indexOf(record.localId)===-1){recordList.push.call(recordList._proxy,record);}});res.push(rec);}
return res.length===1?res[0]:res;});}
delete(...records){const recordList=toRaw(this)._raw;const store=recordList._store;return store.MAKE_UPDATE(function recordListDelete(){for(const val of records){recordList._.insert(recordList,val,function recordListDelete_Insert(record){const index=recordList.data.indexOf(record.localId);if(index!==-1){recordList.splice.call(recordList._proxy,index,1);}},{mode:"DELETE"});}});}
clear(){const recordList=toRaw(this)._raw;const store=recordList._store;return store.MAKE_UPDATE(function recordListClear(){while(recordList.data.length>0){recordList.pop.call(recordList._proxy);}});}*[Symbol.iterator](){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);for(const localId of recordListFullProxy.data){yield recordListFullProxy._store.recordByLocalId.get(localId);}}
at(index){const recordList=toRaw(this)._raw;const recordListFullProxy=recordList._.downgradeProxy(recordList,this);return recordListFullProxy._store.recordByLocalId.get(recordListFullProxy.data.at(index));}}
return __exports;});;

/* /mail/static/src/model/record_uses.js */
odoo.define('@mail/model/record_uses',[],function(require){'use strict';let __exports={};const RecordUses=__exports.RecordUses=class RecordUses{data=new Map();add(list){const record=list._.owner;if(!this.data.has(record.localId)){this.data.set(record.localId,new Map());}
const use=this.data.get(record.localId);if(!use.get(list._.name)){use.set(list._.name,0);}
use.set(list._.name,use.get(list._.name)+1);}
delete(list){const record=list._.owner;if(!this.data.has(record.localId)){return;}
const use=this.data.get(record.localId);if(!use.get(list._.name)){return;}
use.set(list._.name,use.get(list._.name)-1);if(use.get(list._.name)===0){use.delete(list._.name);}}}
return __exports;});;

/* /mail/static/src/model/store.js */
odoo.define('@mail/model/store',['@mail/model/record','@mail/model/misc','@odoo/owl'],function(require){'use strict';let __exports={};const{Record}=require("@mail/model/record");const{STORE_SYM}=require("@mail/model/misc");const{reactive,toRaw}=require("@odoo/owl");const Store=__exports.Store=class Store extends Record{_;[STORE_SYM]=true;recordByLocalId;storeReady=false;get(localId){return this.recordByLocalId.get(localId);}
MAKE_UPDATE(fn){this._.UPDATE++;const res=fn();this._.UPDATE--;const deletingRecordsByLocalId=new Map();if(this._.UPDATE===0){this._.UPDATE++;while(this._.FC_QUEUE.size>0||this._.FS_QUEUE.size>0||this._.FA_QUEUE.size>0||this._.FD_QUEUE.size>0||this._.FU_QUEUE.size>0||this._.RO_QUEUE.size>0||this._.RD_QUEUE.size>0||this._.RHD_QUEUE.size>0){const FC_QUEUE=new Map(this._.FC_QUEUE);const FS_QUEUE=new Map(this._.FS_QUEUE);const FA_QUEUE=new Map(this._.FA_QUEUE);const FD_QUEUE=new Map(this._.FD_QUEUE);const FU_QUEUE=new Map(this._.FU_QUEUE);const RO_QUEUE=new Map(this._.RO_QUEUE);const RD_QUEUE=new Map(this._.RD_QUEUE);const RHD_QUEUE=new Map(this._.RHD_QUEUE);this._.FC_QUEUE.clear();this._.FS_QUEUE.clear();this._.FA_QUEUE.clear();this._.FD_QUEUE.clear();this._.FU_QUEUE.clear();this._.RO_QUEUE.clear();this._.RD_QUEUE.clear();this._.RHD_QUEUE.clear();while(FC_QUEUE.size>0){const[record,recMap]=FC_QUEUE.entries().next().value;FC_QUEUE.delete(record);for(const fieldName of recMap.keys()){record._.requestCompute(record,fieldName,{force:true});}}
while(FS_QUEUE.size>0){const[record,recMap]=FS_QUEUE.entries().next().value;FS_QUEUE.delete(record);for(const fieldName of recMap.keys()){record._.requestSort(record,fieldName,{force:true});}}
while(FA_QUEUE.size>0){const[record,recMap]=FA_QUEUE.entries().next().value;FA_QUEUE.delete(record);while(recMap.size>0){const[fieldName,fieldMap]=recMap.entries().next().value;recMap.delete(fieldName);const onAdd=record.Model._.fieldsOnAdd.get(fieldName);for(const addedRec of fieldMap.keys()){onAdd?.call(record._proxy,addedRec._proxy);}}}
while(FD_QUEUE.size>0){const[record,recMap]=FD_QUEUE.entries().next().value;FD_QUEUE.delete(record);while(recMap.size>0){const[fieldName,fieldMap]=recMap.entries().next().value;recMap.delete(fieldName);const onDelete=record.Model._.fieldsOnDelete.get(fieldName);for(const removedRec of fieldMap.keys()){onDelete?.call(record._proxy,removedRec._proxy);}}}
while(FU_QUEUE.size>0){const[record,map]=FU_QUEUE.entries().next().value;FU_QUEUE.delete(record);for(const fieldName of map.keys()){record._.onUpdate(record,fieldName);}}
while(RO_QUEUE.size>0){const cb=RO_QUEUE.keys().next().value;RO_QUEUE.delete(cb);cb();}
while(RD_QUEUE.size>0){const record=RD_QUEUE.keys().next().value;RD_QUEUE.delete(record);for(const[localId,names]of record._.uses.data.entries()){for(const[name2,count]of names.entries()){const usingRecord2=toRaw(this.recordByLocalId).get(localId)||deletingRecordsByLocalId.get(localId);if(!usingRecord2){record._.uses.data.delete(localId);continue;}
if(usingRecord2.Model._.fieldsMany.get(name2)){for(let c=0;c<count;c++){usingRecord2[name2].delete(record);}}else{usingRecord2[name2]=undefined;}}}
deletingRecordsByLocalId.set(record.localId,record);this.recordByLocalId.delete(record.localId);this._.ADD_QUEUE("hard_delete",toRaw(record));}
while(RHD_QUEUE.size>0){const record=RHD_QUEUE.keys().next().value;RHD_QUEUE.delete(record);deletingRecordsByLocalId.delete(record.localId);}}
this._.UPDATE--;}
return res;}
onChange(record,name,cb){return this._onChange(record,name,(observe)=>{const fn=()=>{observe();cb();};if(this._.UPDATE!==0){if(!this._.RO_QUEUE.has(fn)){this._.RO_QUEUE.set(fn,true);}}else{fn();}});}
_onChange(record,key,callback){let proxy;function _observe(){const val=proxy[key];if(typeof val==="object"&&val!==null){void Object.keys(val);}
if(Array.isArray(val)){void val.length;void toRaw(val).forEach.call(val,(i)=>i);}}
if(Array.isArray(key)){for(const k of key){this._onChange(record,k,callback);}
return;}
let ready=true;proxy=reactive(record,()=>{if(ready){callback(_observe);}});_observe();return()=>{ready=false;};}}
return __exports;});;

/* /mail/static/src/model/store_internal.js */
odoo.define('@mail/model/store_internal',['@odoo/owl','@mail/model/record_internal','@web/core/l10n/dates','@mail/model/misc'],function(require){'use strict';let __exports={};const{markup,toRaw}=require("@odoo/owl");const{RecordInternal}=require("@mail/model/record_internal");const{deserializeDate,deserializeDateTime}=require("@web/core/l10n/dates");const{IS_DELETED_SYM,IS_DELETING_SYM,Markup,isCommand,isMany}=require("@mail/model/misc");const StoreInternal=__exports.StoreInternal=class StoreInternal extends RecordInternal{trusted=false;FC_QUEUE=new Map();FS_QUEUE=new Map();FA_QUEUE=new Map();FD_QUEUE=new Map();FU_QUEUE=new Map();RO_QUEUE=new Map();RD_QUEUE=new Map();RHD_QUEUE=new Map();UPDATE=0;ADD_QUEUE(type,...params){switch(type){case"delete":{const[record]=params;if(!this.RD_QUEUE.has(record)){this.RD_QUEUE.set(record,true);}
break;}
case"compute":{const[record,fieldName]=params;let recMap=this.FC_QUEUE.get(record);if(!recMap){recMap=new Map();this.FC_QUEUE.set(record,recMap);}
recMap.set(fieldName,true);break;}
case"sort":{const[record,fieldName]=params;let recMap=this.FS_QUEUE.get(record);if(!recMap){recMap=new Map();this.FS_QUEUE.set(record,recMap);}
recMap.set(fieldName,true);break;}
case"onAdd":{const[record,fieldName,addedRec]=params;const Model=record.Model;if(Model._.fieldsSort.get(fieldName)){this.ADD_QUEUE("sort",record,fieldName);}
if(!Model._.fieldsOnAdd.get(fieldName)){return;}
let recMap=this.FA_QUEUE.get(record);if(!recMap){recMap=new Map();this.FA_QUEUE.set(record,recMap);}
let fieldMap=recMap.get(fieldName);if(!fieldMap){fieldMap=new Map();recMap.set(fieldName,fieldMap);}
fieldMap.set(addedRec,true);break;}
case"onDelete":{const[record,fieldName,removedRec]=params;const Model=record.Model;if(!Model._.fieldsOnDelete.get(fieldName)){return;}
let recMap=this.FD_QUEUE.get(record);if(!recMap){recMap=new Map();this.FD_QUEUE.set(record,recMap);}
let fieldMap=recMap.get(fieldName);if(!fieldMap){fieldMap=new Map();recMap.set(fieldName,fieldMap);}
fieldMap.set(removedRec,true);break;}
case"onUpdate":{const[record,fieldName]=params;let recMap=this.FU_QUEUE.get(record);if(!recMap){recMap=new Map();this.FU_QUEUE.set(record,recMap);}
recMap.set(fieldName,true);break;}
case"hard_delete":{const[record]=params;record._[IS_DELETING_SYM]=true;record._proxy[IS_DELETED_SYM]=true;delete record.Model.records[record.localId];if(!this.RHD_QUEUE.has(record)){this.RHD_QUEUE.set(record,true);}
break;}}}
sortRecordList(recordListFullProxy,func){const recordList=toRaw(recordListFullProxy)._raw;const recordsFullProxy=recordListFullProxy.data.map((localId)=>recordListFullProxy._store.recordByLocalId.get(localId));recordsFullProxy.sort(func);const data=recordsFullProxy.map((recordFullProxy)=>toRaw(recordFullProxy)._raw.localId);const hasChanged=recordList.data.some((localId,i)=>localId!==data[i]);if(hasChanged){recordListFullProxy.data=data;}}
updateAttr(record,fieldName,value){const Model=record.Model;const fieldType=Model._.fieldsType.get(fieldName);const fieldHtml=Model._.fieldsHtml.get(fieldName);const targetRecord=record._.proxyUsed.has(fieldName)?record:record._proxy;let shouldChange=record[fieldName]!==value;if(fieldType==="datetime"&&value){if(!(value instanceof luxon.DateTime)){value=deserializeDateTime(value);}
shouldChange=!record[fieldName]||!value.equals(record[fieldName]);}
if(fieldType==="date"&&value){if(!(value instanceof luxon.DateTime)){value=deserializeDate(value);}
shouldChange=!record[fieldName]||!value.equals(record[fieldName]);}
let newValue=value;if(fieldHtml&&this.trusted){shouldChange=record[fieldName]?.toString()!==value?.toString()||!(record[fieldName]instanceof Markup);newValue=typeof value==="string"?markup(value):value;}
if(shouldChange){record._.updatingAttrs.set(fieldName,true);targetRecord[fieldName]=newValue;record._.updatingAttrs.delete(fieldName);}}
updateFields(record,vals){const fieldEntries=Object.entries(vals).concat(Object.getOwnPropertySymbols(vals).map((sym)=>[sym,vals[sym]]));for(const[fieldName,value]of fieldEntries){if(!record.Model._.fields.get(fieldName)||record.Model._.fieldsAttr.get(fieldName)){this.updateAttr(record,fieldName,value);}else{this.updateRelation(record,fieldName,value);}}}
updateRelation(record,fieldName,value){const recordList=record[fieldName];if(isMany(record.Model,fieldName)){this.updateRelationMany(recordList,value);}else{this.updateRelationOne(recordList,value);}}
updateRelationMany(recordList,value){if(isCommand(value)){for(const[cmd,cmdData]of value){if(Array.isArray(cmdData)){for(const item of cmdData){if(cmd==="ADD"){recordList.add(item);}else if(cmd==="ADD.noinv"){recordList._.addNoinv(recordList,item);}else if(cmd==="DELETE.noinv"){recordList._.deleteNoinv(recordList,item);}else{recordList.delete(item);}}}else{if(cmd==="ADD"){recordList.add(cmdData);}else if(cmd==="ADD.noinv"){recordList._.addNoinv(recordList,cmdData);}else if(cmd==="DELETE.noinv"){recordList._.deleteNoinv(recordList,cmdData);}else{recordList.delete(cmdData);}}}}else if([null,false,undefined].includes(value)){recordList.clear();}else if(!Array.isArray(value)){recordList._.assign(recordList,[value]);}else{recordList._.assign(recordList,value);}}
updateRelationOne(recordList,value){if(isCommand(value)){const[cmd,cmdData]=value.at(-1);if(cmd==="ADD"){recordList.add(cmdData);}else if(cmd==="ADD.noinv"){recordList._.addNoinv(recordList,cmdData);}else if(cmd==="DELETE.noinv"){recordList._.deleteNoinv(recordList,cmdData);}else{recordList.delete(cmdData);}}else if([null,false,undefined].includes(value)){recordList.clear();}else{recordList.add(value);}}}
return __exports;});;

/* /mail/static/src/core/common/attachment_list.js */
odoo.define('@mail/core/common/attachment_list',['@odoo/owl','@web/core/browser/feature_detection','@web/core/confirmation_dialog/confirmation_dialog','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_hooks','@web/core/dropdown/dropdown_item','@web/core/file_viewer/file_viewer_hook','@web/core/l10n/translation','@web/core/utils/hooks','@web/core/utils/urls'],function(require){'use strict';let __exports={};const{Component,useState}=require("@odoo/owl");const{isMobileOS}=require("@web/core/browser/feature_detection");const{ConfirmationDialog}=require("@web/core/confirmation_dialog/confirmation_dialog");const{Dropdown}=require("@web/core/dropdown/dropdown");const{useDropdownState}=require("@web/core/dropdown/dropdown_hooks");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{useFileViewer}=require("@web/core/file_viewer/file_viewer_hook");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{url}=require("@web/core/utils/urls");class ImageActions extends Component{static components={Dropdown,DropdownItem};static props=["actions","imagesHeight"];static template="mail.ImageActions";setup(){super.setup();this.actionsMenuState=useDropdownState();this.isMobileOS=isMobileOS();}}
const AttachmentList=__exports.AttachmentList=class AttachmentList extends Component{static components={ImageActions};static props=["attachments","unlinkAttachment","imagesHeight","messageSearch?"];static template="mail.AttachmentList";setup(){super.setup();this.ui=useState(useService("ui"));this.imagesWidth=1920;this.dialog=useService("dialog");this.fileViewer=useFileViewer();this.actionsMenuState=useDropdownState();this.isMobileOS=isMobileOS();}
getImageUrl(attachment){if(attachment.uploading&&attachment.tmpUrl){return attachment.tmpUrl;}
return url(attachment.urlRoute,{...attachment.urlQueryParams,width:this.imagesWidth*2,height:this.props.imagesHeight*2,});}
get images(){return this.props.attachments.filter((a)=>a.isImage);}
get cards(){return this.props.attachments.filter((a)=>!a.isImage);}
canDownload(attachment){return!attachment.uploading&&!this.env.inComposer;}
onClickDownload(attachment){const downloadLink=document.createElement("a");downloadLink.setAttribute("href",attachment.downloadUrl);downloadLink.setAttribute("download","");downloadLink.click();}
onClickUnlink(attachment){if(this.env.inComposer){return this.props.unlinkAttachment(attachment);}
this.dialog.add(ConfirmationDialog,{body:_t('Do you really want to delete "%s"?',attachment.filename),cancel:()=>{},confirm:()=>this.onConfirmUnlink(attachment),});}
onConfirmUnlink(attachment){this.props.unlinkAttachment(attachment);}
onImageLoaded(){this.env.onImageLoaded?.();}
get isInChatWindowAndIsAlignedRight(){return this.env.inChatWindow&&this.env.alignedRight;}
get isInChatWindowAndIsAlignedLeft(){return this.env.inChatWindow&&!this.env.alignedRight;}
getActions(attachment){const res=[];if(this.showDelete){res.push({label:_t("Remove"),icon:"fa fa-trash",onSelect:()=>this.onClickUnlink(attachment),});}
if(this.canDownload(attachment)){res.push({label:_t("Download"),icon:"fa fa-download",onSelect:()=>this.onClickDownload(attachment),});}
return res;}
get showDelete(){if(this.env.inComposer){return true;}
if(!this.attachment.isDeletable){return false;}
return(!this.env.message||this.env.message.hasTextContent||(this.env.message&&this.props.attachments.length>1));}}
return __exports;});;

/* /mail/static/src/core/common/attachment_model.js */
odoo.define('@mail/core/common/attachment_model',['@mail/core/common/record','@mail/utils/common/misc','@web/core/network/rpc','@web/core/file_viewer/file_model'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const{assignDefined}=require("@mail/utils/common/misc");const{rpc}=require("@web/core/network/rpc");const{FileModelMixin}=require("@web/core/file_viewer/file_model");const Attachment=__exports.Attachment=class Attachment extends FileModelMixin(Record){static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
static new(){const attachment=super.new(...arguments);Record.onChange(attachment,["extension","filename"],()=>{if(!attachment.extension&&attachment.filename){attachment.extension=attachment.filename.split(".").pop();}});return attachment;}
thread=Record.one("Thread",{inverse:"attachments"});res_name;message=Record.one("Message",{inverse:"attachment_ids"});create_date=Record.attr(undefined,{type:"datetime"});get isDeletable(){if(this.message&&!this.store.self.isInternalUser){return this.message.editable;}
return true;}
get monthYear(){if(!this.create_date){return undefined;}
return`${this.create_date.monthLong}, ${this.create_date.year}`;}
get uploading(){return this.id<0;}
delete(){if(this.tmpUrl){URL.revokeObjectURL(this.tmpUrl);}
super.delete();}
async remove(){if(this.id>0){const rpcParams=assignDefined({attachment_id:this.id},{access_token:this.access_token});const thread=this.thread||this.message?.thread;if(thread){Object.assign(rpcParams,thread.rpcParams);}
await rpc("/mail/attachment/delete",rpcParams);}
this.delete();}}
Attachment.register();return __exports;});;

/* /mail/static/src/core/common/attachment_upload_service.js */
odoo.define('@mail/core/common/attachment_upload_service',['@odoo/owl','@web/core/l10n/translation','@web/core/registry','@web/core/utils/concurrency'],function(require){'use strict';let __exports={};const{EventBus}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{Deferred}=require("@web/core/utils/concurrency");const AttachmentUploadService=__exports.AttachmentUploadService=class AttachmentUploadService{constructor(env,services){this.setup(env,services);}
setup(env,services){this.env=env;this.fileUploadService=services["file_upload"];this.store=services["mail.store"];this.notificationService=services["notification"];this.nextId=-1;this.abortByAttachmentId=new Map();this.deferredByAttachmentId=new Map();this.uploadingAttachmentIds=new Set();this._fileUploadBus=new EventBus();this.targetsByTmpId=new Map();this.fileUploadService.bus.addEventListener("FILE_UPLOAD_ADDED",({detail:{upload}})=>{const tmpId=parseInt(upload.data.get("temporary_id"));if(!this.uploadingAttachmentIds.has(tmpId)){return;}
const{thread,composer}=this.targetsByTmpId.get(tmpId);const tmpUrl=upload.data.get("tmp_url");this.abortByAttachmentId.set(tmpId,upload.xhr.abort.bind(upload.xhr));const attachment=this.store.Attachment.insert(this._makeAttachmentData(upload,tmpId,composer?undefined:thread,tmpUrl));composer?.attachments.push(attachment);});this.fileUploadService.bus.addEventListener("FILE_UPLOAD_LOADED",({detail:{upload}})=>{const tmpId=parseInt(upload.data.get("temporary_id"));if(!this.uploadingAttachmentIds.has(tmpId)){return;}
const def=this.deferredByAttachmentId.get(tmpId);if(upload.xhr.status===413){this.notificationService.add(_t("File too large"),{type:"danger"});def.resolve();this._cleanupUploading(tmpId);return;}
if(upload.xhr.status!==200){this.notificationService.add(_t("Server error"),{type:"danger"});def.resolve();this._cleanupUploading(tmpId);return;}
const response=JSON.parse(upload.xhr.response);if(response.error){this.notificationService.add(response.error,{type:"danger"});def.resolve();this._cleanupUploading(tmpId);return;}
const{thread,composer}=this.targetsByTmpId.get(tmpId);this._processLoaded(thread,composer,response,tmpId,def);});this.fileUploadService.bus.addEventListener("FILE_UPLOAD_ERROR",({detail:{upload}})=>{const tmpId=parseInt(upload.data.get("temporary_id"));if(!this.uploadingAttachmentIds.has(tmpId)){return;}
this.deferredByAttachmentId.get(tmpId).resolve();this._cleanupUploading(tmpId);});}
_processLoaded(thread,composer,{data},tmpId,def){const{Attachment}=this.store.insert(data);const[attachment]=Attachment;if(composer){const index=composer.attachments.findIndex(({id})=>id===tmpId);if(index>=0){composer.attachments[index]=attachment;}else{composer.attachments.push(attachment);}}
def.resolve(attachment);this._fileUploadBus.trigger("UPLOAD",thread);this._cleanupUploading(tmpId);}
_cleanupUploading(tmpId){this.abortByAttachmentId.delete(tmpId);this.deferredByAttachmentId.delete(tmpId);this.uploadingAttachmentIds.delete(tmpId);this.targetsByTmpId.delete(tmpId);this.store.Attachment.get(tmpId)?.remove();}
getUploadURL(thread){return"/mail/attachment/upload";}
async unlink(attachment){if(this.uploadingAttachmentIds.has(attachment.id)){const deferred=this.deferredByAttachmentId.get(attachment.id);const abort=this.abortByAttachmentId.get(attachment.id);this._cleanupUploading(attachment.id);deferred.resolve();abort();return;}
await attachment.remove();}
async upload(thread,composer,file,options){const tmpId=this.nextId--;const tmpURL=URL.createObjectURL(file);return this._upload(thread,composer,file,options,tmpId,tmpURL);}
async _upload(thread,composer,file,options,tmpId,tmpURL){this.targetsByTmpId.set(tmpId,{composer,thread});this.uploadingAttachmentIds.add(tmpId);await this.fileUploadService.upload(this.getUploadURL(thread),[file],{buildFormData:(formData)=>{this._buildFormData(formData,tmpURL,thread,composer,tmpId,options);},}).catch((e)=>{if(e.name!=="AbortError"){throw e;}});const uploadDoneDeferred=new Deferred();this.deferredByAttachmentId.set(tmpId,uploadDoneDeferred);return uploadDoneDeferred;}
onFileUploaded(thread,onFileUploaded){this._fileUploadBus.addEventListener("UPLOAD",({detail})=>{if(thread.eq(detail)){onFileUploaded();}});}
_buildFormData(formData,tmpURL,thread,composer,tmpId,options){formData.append("thread_id",thread.id);formData.append("tmp_url",tmpURL);formData.append("thread_model",thread.model);formData.append("is_pending",Boolean(composer));formData.append("temporary_id",tmpId);if(options?.activity){formData.append("activity_id",options.activity.id);}
return formData;}
_makeAttachmentData(upload,tmpId,thread,tmpUrl){const attachmentData={filename:upload.title,id:tmpId,mimetype:upload.type,name:upload.title,resModel:upload.res_model,thread,extension:upload.title.split(".").pop(),uploading:true,tmpUrl,};return attachmentData;}}
const attachmentUploadService=__exports.attachmentUploadService={dependencies:["file_upload","mail.store","notification"],start(env,services){return new AttachmentUploadService(env,services);},};registry.category("services").add("mail.attachment_upload",attachmentUploadService);return __exports;});;

/* /mail/static/src/core/common/attachment_uploader_hook.js */
odoo.define('@mail/core/common/attachment_uploader_hook',['@odoo/owl','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{useState}=require("@odoo/owl");const{useService}=require("@web/core/utils/hooks");__exports.dataUrlToBlob=dataUrlToBlob;function dataUrlToBlob(data,type){const binData=window.atob(data);const uiArr=new Uint8Array(binData.length);uiArr.forEach((_,index)=>(uiArr[index]=binData.charCodeAt(index)));return new Blob([uiArr],{type});}
const AttachmentUploader=__exports.AttachmentUploader=class AttachmentUploader{constructor(thread,{composer}={}){this.attachmentUploadService=useService("mail.attachment_upload");Object.assign(this,{thread,composer});}
uploadData({data,name,type},options){const file=new File([dataUrlToBlob(data,type)],name,{type});return this.uploadFile(file,options);}
async uploadFile(file,options){const thread=options?.thread||this.thread;return this.attachmentUploadService.upload(thread,this.composer,file,options);}
async unlink(attachment){await this.attachmentUploadService.unlink(attachment);}}
__exports.useAttachmentUploader=useAttachmentUploader;function useAttachmentUploader(thread,{composer,onFileUploaded}={}){return useState(new AttachmentUploader(...arguments));}
return __exports;});;

/* /mail/static/src/core/common/attachment_view.js */
odoo.define('@mail/core/common/attachment_view',['@odoo/owl','@web/core/utils/hooks','@web/core/utils/objects','@web/libs/pdfjs'],function(require){'use strict';let __exports={};const{Component,onMounted,onWillUnmount,onWillUpdateProps,useComponent,useEffect,useRef,useState,}=require("@odoo/owl");const{useService}=require("@web/core/utils/hooks");const{deepEqual}=require("@web/core/utils/objects");const{hidePDFJSButtons}=require("@web/libs/pdfjs");class AbstractAttachmentView extends Component{static template="mail.AttachmentView";static components={};static props=["threadId","threadModel"];setup(){super.setup();this.store=useState(useService("mail.store"));this.uiService=useService("ui");this.iframeViewerPdfRef=useRef("iframeViewerPdf");this.state=useState({thread:undefined,});useEffect(()=>{if(this.iframeViewerPdfRef.el){hidePDFJSButtons(this.iframeViewerPdfRef.el);}});this.updateFromProps(this.props);onWillUpdateProps((props)=>this.updateFromProps(props));}
onClickNext(){const index=this.state.thread.attachmentsInWebClientView.findIndex((attachment)=>attachment.eq(this.state.thread.mainAttachment));this.state.thread.setMainAttachmentFromIndex(index>=this.state.thread.attachmentsInWebClientView.length-1?0:index+1);}
onClickPrevious(){const index=this.state.thread.attachmentsInWebClientView.findIndex((attachment)=>attachment.eq(this.state.thread.mainAttachment));this.state.thread.setMainAttachmentFromIndex(index<=0?this.state.thread.attachmentsInWebClientView.length-1:index-1);}
updateFromProps(props){this.state.thread=this.store.Thread.insert({id:props.threadId,model:props.threadModel,});}
get displayName(){return this.state.thread.mainAttachment.filename;}
onClickPopout(){}}
const PopoutAttachmentView=__exports.PopoutAttachmentView=class PopoutAttachmentView extends AbstractAttachmentView{static template="mail.PopoutAttachmentView";}
__exports.usePopoutAttachment=usePopoutAttachment;function usePopoutAttachment(){const component=useComponent();const uiService=useService("ui");const mailPopoutService=useService("mail.popout");function attachmentViewParentElementClassList(){const attachmentViewEl=document.querySelector(".o-mail-Attachment");let parentElementClassList;if((parentElementClassList=attachmentViewEl?.parentElement?.classList)){return parentElementClassList;}
return null;}
function showAttachmentView(){const parentElementClassList=attachmentViewParentElementClassList();const hiddenClass="d-none";if(parentElementClassList?.contains(hiddenClass)){parentElementClassList.remove(hiddenClass);}}
function hideAttachmentView(){const parentElementClassList=attachmentViewParentElementClassList();const hiddenClass="d-none";if(!parentElementClassList?.contains(hiddenClass)){parentElementClassList?.add(hiddenClass);}}
function extractPopoutProps(props){return{threadId:props.threadId,threadModel:props.threadModel,};}
function popout(){mailPopoutService.addHooks(()=>{hideAttachmentView();uiService.bus.trigger("resize");},()=>{showAttachmentView();uiService.bus.trigger("resize");});mailPopoutService.popout(PopoutAttachmentView,extractPopoutProps(component.props));}
function updatePopout(newProps=component.props){if(mailPopoutService.externalWindow){hideAttachmentView();mailPopoutService.popout(PopoutAttachmentView,extractPopoutProps(newProps));}}
function resetPopout(){mailPopoutService.reset();}
onMounted(updatePopout);onWillUpdateProps((props)=>{const oldProps=extractPopoutProps(component.props);const newProps=extractPopoutProps(props);if(!deepEqual(oldProps,newProps)){updatePopout(newProps);}});onWillUnmount(resetPopout);return{popout,updatePopout,resetPopout,};}
const AttachmentView=__exports.AttachmentView=class AttachmentView extends AbstractAttachmentView{setup(){super.setup();this.attachmentPopout=usePopoutAttachment();}
onClickPopout(){this.attachmentPopout.popout();}}
return __exports;});;

/* /mail/static/src/core/common/autoresize_input.js */
odoo.define('@mail/core/common/autoresize_input',['@odoo/owl','@web/core/utils/autoresize'],function(require){'use strict';let __exports={};const{Component,useRef,useState,onWillUpdateProps,onMounted}=require("@odoo/owl");const{useAutoresize}=require("@web/core/utils/autoresize");const AutoresizeInput=__exports.AutoresizeInput=class AutoresizeInput extends Component{static template="mail.AutoresizeInput";static props={autofocus:{type:Boolean,optional:true},className:{type:String,optional:true},enabled:{optional:true},onValidate:{type:Function,optional:true},placeholder:{type:String,optional:true},value:{type:String,optional:true},};static defaultProps={autofocus:false,className:"",enabled:true,onValidate:()=>{},placeholder:"",};setup(){super.setup();this.state=useState({value:this.props.value,isFocused:false,});this.inputRef=useRef("input");onWillUpdateProps((nextProps)=>{if(this.props.value!==nextProps.value){this.state.value=nextProps.value;}});useAutoresize(this.inputRef);onMounted(()=>{if(this.props.autofocus){this.inputRef.el.focus();this.inputRef.el.setSelectionRange(-1,-1);}});}
onKeydownInput(ev){switch(ev.key){case"Enter":this.inputRef.el.blur();break;case"Escape":ev.stopPropagation();this.state.value=this.props.value;this.inputRef.el.blur();break;}}
onBlurInput(){this.state.isFocused=false;this.props.onValidate(this.state.value);}}
return __exports;});;

/* /mail/static/src/core/common/canned_response_model.js */
odoo.define('@mail/core/common/canned_response_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const CannedResponse=__exports.CannedResponse=class CannedResponse extends Record{static _name="mail.canned.response";static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
id;source;substitution;}
CannedResponse.register();return __exports;});;

/* /mail/static/src/core/common/channel_member_model.js */
odoo.define('@mail/core/common/channel_member_model',['@mail/core/common/store_service','@mail/core/common/record','@web/core/browser/browser','@web/core/l10n/dates','@web/core/user'],function(require){'use strict';let __exports={};const{Store}=require("@mail/core/common/store_service");const{Record}=require("@mail/core/common/record");const{browser}=require("@web/core/browser/browser");const{deserializeDateTime}=require("@web/core/l10n/dates");const{user}=require("@web/core/user");const{DateTime}=luxon;const ChannelMember=__exports.ChannelMember=class ChannelMember extends Record{static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
create_date;id;last_interest_dt=Record.attr(undefined,{type:"datetime"});last_seen_dt=Record.attr(undefined,{type:"datetime"});persona=Record.one("Persona",{inverse:"channelMembers"});thread=Record.one("Thread",{inverse:"channelMembers"});threadAsSelf=Record.one("Thread",{compute(){if(this.store.self?.eq(this.persona)){return this.thread;}},});fetched_message_id=Record.one("Message");seen_message_id=Record.one("Message");syncUnread=true;_syncUnread=Record.attr(false,{compute(){if(!this.syncUnread||!this.eq(this.thread?.selfMember)){return false;}
return(this.localNewMessageSeparator!==this.new_message_separator||this.localMessageUnreadCounter!==this.message_unread_counter);},onUpdate(){if(this._syncUnread){this.localNewMessageSeparator=this.new_message_separator;this.localMessageUnreadCounter=this.message_unread_counter;}},});unreadSynced=Record.attr(true,{compute(){return this.localNewMessageSeparator===this.new_message_separator;},onUpdate(){if(this.unreadSynced){this.hideUnreadBanner=false;}},});hideUnreadBanner=false;localMessageUnreadCounter=0;localNewMessageSeparator=null;message_unread_counter=0;message_unread_counter_bus_id=0;new_message_separator=null;isTyping=false;is_typing_dt=Record.attr(undefined,{type:"datetime",onUpdate(){browser.clearTimeout(this.typingTimeoutId);if(!this.is_typing_dt){this.isTyping=false;}
if(this.isTyping){this.typingTimeoutId=browser.setTimeout(()=>(this.isTyping=false),Store.OTHER_LONG_TYPING);}},});threadAsTyping=Record.one("Thread",{compute(){return this.isTyping?this.thread:undefined;},eager:true,onDelete(){browser.clearTimeout(this.typingTimeoutId);},});typingTimeoutId;get name(){return this.persona.name;}
getLangName(){return this.persona.lang_name;}
get memberSince(){return this.create_date?deserializeDateTime(this.create_date):undefined;}
hasSeen(message){return this.persona.eq(message.author)||this.seen_message_id?.id>=message.id;}
get lastSeenDt(){return this.last_seen_dt?this.last_seen_dt.toLocaleString(DateTime.TIME_24_SIMPLE,{locale:user.lang,}):undefined;}
get totalUnreadMessageCounter(){let counter=this.message_unread_counter;if(!this.unreadSynced){counter+=this.localMessageUnreadCounter;}
return counter;}}
ChannelMember.register();return __exports;});;

/* /mail/static/src/core/common/chat_bubble.js */
odoo.define('@mail/core/common/chat_bubble',['@mail/core/common/im_status','@odoo/owl','@web/core/utils/hooks','@mail/utils/common/hooks','@web/core/dropdown/dropdown_hooks','@web/core/dropdown/dropdown','@mail/core/common/country_flag'],function(require){'use strict';let __exports={};const{ImStatus}=require("@mail/core/common/im_status");const{Component,useEffect,useRef,useState}=require("@odoo/owl");const{useService}=require("@web/core/utils/hooks");const{useHover,useMovable}=require("@mail/utils/common/hooks");const{useDropdownState}=require("@web/core/dropdown/dropdown_hooks");const{Dropdown}=require("@web/core/dropdown/dropdown");const{CountryFlag}=require("@mail/core/common/country_flag");const ChatBubble=__exports.ChatBubble=class ChatBubble extends Component{static components={CountryFlag,ImStatus,Dropdown};static props=["chatWindow"];static template="mail.ChatBubble";setup(){super.setup();this.store=useState(useService("mail.store"));this.wasHover=false;this.hover=useHover(["root","preview*"],{onHover:()=>(this.preview.isOpen=true),onHovering:[100,()=>(this.state.showClose=true)],onAway:()=>{this.state.showClose=false;this.preview.isOpen=false;},});this.preview=useDropdownState();this.rootRef=useRef("root");this.state=useState({bouncing:false,showClose:true});useEffect((importantCounter)=>{this.state.bouncing=Boolean(importantCounter);},()=>[this.thread?.importantCounter]);if(this.env.embedLivechat){this.position=useState({left:"auto",top:"auto"});useMovable({cursor:"grabbing",ref:this.rootRef,elements:".o-mail-ChatBubble",onDrop:({top,left})=>Object.assign(this.position,{left:`${left}px`,top:`${top}px`}),});}}
get thread(){return this.props.chatWindow.thread;}
get previewContent(){const lastMessage=this.thread?.newestPersistentNotEmptyOfAllMessage;if(!lastMessage){return false;}
return lastMessage.inlineBody;}}
return __exports;});;

/* /mail/static/src/core/common/chat_hub.js */
odoo.define('@mail/core/common/chat_hub',['@mail/core/common/chat_window','@mail/utils/common/hooks','@odoo/owl','@web/core/browser/browser','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_hooks','@web/core/registry','@web/core/utils/hooks','@mail/core/common/chat_bubble','@web/core/browser/feature_detection'],function(require){'use strict';let __exports={};const{ChatWindow}=require("@mail/core/common/chat_window");const{useHover,useMovable}=require("@mail/utils/common/hooks");const{Component,useEffect,useExternalListener,useRef,useState}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{Dropdown}=require("@web/core/dropdown/dropdown");const{useDropdownState}=require("@web/core/dropdown/dropdown_hooks");const{registry}=require("@web/core/registry");const{useService}=require("@web/core/utils/hooks");const{ChatBubble}=require("@mail/core/common/chat_bubble");const{isMobileOS}=require("@web/core/browser/feature_detection");const ChatHub=__exports.ChatHub=class ChatHub extends Component{static components={ChatBubble,ChatWindow,Dropdown};static props=[];static template="mail.ChatHub";get chatHub(){return this.store.chatHub;}
setup(){super.setup();this.store=useState(useService("mail.store"));this.ui=useState(useService("ui"));this.busMonitoring=useState(useService("bus.monitoring_service"));this.bubblesHover=useHover("bubbles");this.moreHover=useHover(["more-button","more-menu*"],{onHover:()=>(this.more.isOpen=true),onAway:()=>(this.more.isOpen=false),});this.options=useDropdownState();this.more=useDropdownState();this.compactRef=useRef("compact");this.compactPosition=useState({left:"auto",top:"auto"});this.onResize();useExternalListener(browser,"resize",this.onResize);useEffect(()=>{if(this.chatHub.folded.length&&this.store.channels?.status==="not_fetched"){this.store.channels.fetch();}});useMovable({cursor:"grabbing",ref:this.compactRef,elements:".o-mail-ChatHub-compact",onDrop:({top,left})=>Object.assign(this.compactPosition,{left:`${left}px`,top:`${top}px`}),});}
get isMobileOS(){return isMobileOS();}
onResize(){this.chatHub.onRecompute();}
get compactCounter(){let counter=0;const cws=this.chatHub.opened.concat(this.chatHub.folded);for(const chatWindow of cws){counter+=chatWindow.thread.importantCounter>0?1:0;}
return counter;}
get hiddenCounter(){let counter=0;for(const chatWindow of this.chatHub.folded.slice(this.chatHub.maxFolded)){counter+=chatWindow.thread.importantCounter>0?1:0;}
return counter;}
get isShown(){return true;}
expand(){this.chatHub.compact=false;Object.assign(this.compactPosition,{left:"auto",top:"auto"});this.more.isOpen=this.chatHub.folded.length>this.chatHub.maxFolded;}}
const chatHubService=__exports.chatHubService={dependencies:["bus.monitoring_service","mail.store","ui"],start(){registry.category("main_components").add("mail.ChatHub",{Component:ChatHub});},};registry.category("services").add("mail.chat_hub",chatHubService);return __exports;});;

/* /mail/static/src/core/common/chat_hub_model.js */
odoo.define('@mail/core/common/chat_hub_model',['@web/core/browser/browser','@mail/core/common/record'],function(require){'use strict';let __exports={};const{browser}=require("@web/core/browser/browser");const{Record}=require("@mail/core/common/record");const ChatHub=__exports.ChatHub=class ChatHub extends Record{BUBBLE=56;BUBBLE_START=15;BUBBLE_LIMIT=7;BUBBLE_OUTER=10;WINDOW_GAP=10;WINDOW_INBETWEEN=5;WINDOW=380;static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
compact=false;opened=Record.many("ChatWindow",{inverse:"hubAsOpened",onAdd(r){this.onRecompute();},onDelete(){this.onRecompute();},});folded=Record.many("ChatWindow",{inverse:"hubAsFolded",onAdd(r){this.onRecompute();},onDelete(){this.onRecompute();},});closeAll(){[...this.opened,...this.folded].forEach((cw)=>cw.close());}
onRecompute(){while(this.opened.length>this.maxOpened){const cw=this.opened.pop();this.folded.unshift(cw);}}
get maxOpened(){const chatBubblesWidth=this.BUBBLE_START+this.BUBBLE+this.BUBBLE_OUTER*2;const startGap=this.store.env.services.ui.isSmall?0:this.WINDOW_GAP;const endGap=this.store.env.services.ui.isSmall?0:this.WINDOW_GAP;const available=browser.innerWidth-startGap-endGap-chatBubblesWidth;const maxAmountWithoutHidden=Math.max(1,Math.floor(available/(this.WINDOW+this.WINDOW_INBETWEEN)));return maxAmountWithoutHidden;}
get maxFolded(){const chatBubbleSpace=this.BUBBLE_START+this.BUBBLE+this.BUBBLE_OUTER*2;return Math.min(this.BUBBLE_LIMIT,Math.floor(browser.innerHeight/chatBubbleSpace));}}
ChatHub.register();return __exports;});;

/* /mail/static/src/core/common/chat_window.js */
odoo.define('@mail/core/common/chat_window',['@mail/core/common/composer','@mail/core/common/im_status','@mail/core/common/thread','@mail/core/common/autoresize_input','@mail/core/common/country_flag','@mail/core/common/thread_actions','@mail/core/common/thread_icon','@mail/utils/common/hooks','@web/core/utils/misc','@odoo/owl','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_item','@web/core/l10n/localization','@web/core/l10n/translation','@web/core/utils/hooks','@mail/discuss/typing/common/typing','@web/core/hotkeys/hotkey_service'],function(require){'use strict';let __exports={};const{Composer}=require("@mail/core/common/composer");const{ImStatus}=require("@mail/core/common/im_status");const{Thread}=require("@mail/core/common/thread");const{AutoresizeInput}=require("@mail/core/common/autoresize_input");const{CountryFlag}=require("@mail/core/common/country_flag");const{useThreadActions}=require("@mail/core/common/thread_actions");const{ThreadIcon}=require("@mail/core/common/thread_icon");const{useHover,useMessageEdition,useMessageHighlight,useMessageToReplyTo,}=require("@mail/utils/common/hooks");const{isEventHandled}=require("@web/core/utils/misc");const{Component,toRaw,useChildSubEnv,useRef,useState}=require("@odoo/owl");const{Dropdown}=require("@web/core/dropdown/dropdown");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{localization}=require("@web/core/l10n/localization");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{Typing}=require("@mail/discuss/typing/common/typing");const{getActiveHotkey}=require("@web/core/hotkeys/hotkey_service");const ChatWindow=__exports.ChatWindow=class ChatWindow extends Component{static components={CountryFlag,Dropdown,DropdownItem,Thread,Composer,ThreadIcon,ImStatus,AutoresizeInput,Typing,};static props=["chatWindow","right?"];static template="mail.ChatWindow";setup(){super.setup();this.store=useState(useService("mail.store"));this.messageEdition=useMessageEdition();this.messageHighlight=useMessageHighlight();this.messageToReplyTo=useMessageToReplyTo();this.state=useState({actionsDisabled:false,actionsMenuOpened:false,jumpThreadPresent:0,editingGuestName:false,editingName:false,});this.ui=useState(useService("ui"));this.contentRef=useRef("content");this.threadActions=useThreadActions();this.actionsMenuButtonHover=useHover("actionsMenuButton");this.parentChannelHover=useHover("parentChannel");useChildSubEnv({closeActionPanel:()=>this.threadActions.activeAction?.close(),inChatWindow:true,messageHighlight:this.messageHighlight,});}
get composerType(){if(this.thread&&this.thread.model!=="discuss.channel"){return"note";}
return undefined;}
get hasActionsMenu(){return(this.partitionedActions.group.length>0||this.partitionedActions.other.length>0||(this.ui.isSmall&&this.partitionedActions.quick.length>2)||(!this.ui.isSmall&&this.partitionedActions.quick.length>3));}
get thread(){return this.props.chatWindow.thread;}
get style(){const textDirection=localization.direction;const offsetFrom=textDirection==="rtl"?"left":"right";const visibleOffset=this.ui.isSmall?0:this.props.right;const oppositeFrom=offsetFrom==="right"?"left":"right";return`${offsetFrom}: ${visibleOffset}px; ${oppositeFrom}: auto;`;}
onKeydown(ev){const chatWindow=toRaw(this.props.chatWindow);if(ev.key==="Escape"&&this.threadActions.activeAction){this.threadActions.activeAction.close();ev.stopPropagation();return;}
if(ev.target.closest(".o-dropdown")||ev.target.closest(".o-dropdown--menu")){return;}
ev.stopPropagation();switch(getActiveHotkey(ev)){case"escape":if(isEventHandled(ev,"NavigableList.close")||isEventHandled(ev,"Composer.discard")){return;}
if(this.state.editingName){this.state.editingName=false;return;}
this.close({escape:true});break;case"tab":{const index=this.store.chatHub.opened.findIndex((cw)=>cw.eq(chatWindow));if(index===this.store.chatHub.opened.length-1){this.store.chatHub.opened[0].focus();}else{this.store.chatHub.opened[index+1].focus();}
break;}
case"control+k":this.store.env.services.command.openMainPalette();ev.preventDefault();break;}}
onClickHeader(){if(this.ui.isSmall||this.state.editingName||!this.thread||this.state.actionsDisabled){return;}
this.toggleFold();}
toggleFold(){const chatWindow=toRaw(this.props.chatWindow);if(this.state.actionsMenuOpened){return;}
chatWindow.fold();}
async close(options){const chatWindow=toRaw(this.props.chatWindow);await chatWindow.close(options);}
get actionsMenuTitleText(){return _t("Open Actions Menu");}
async renameThread(name){const thread=toRaw(this.thread);await thread.rename(name);this.state.editingName=false;}
async renameGuest(name){const newName=name.trim();if(this.store.self.name!==newName){await this.store.self.updateGuestName(newName);}
this.state.editingGuestName=false;}
async onActionsMenuStateChanged(isOpen){this.state.actionsMenuOpened=isOpen;}}
return __exports;});;

/* /mail/static/src/core/common/chat_window_model.js */
odoo.define('@mail/core/common/chat_window_model',['@mail/core/common/record','@web/core/network/rpc','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const{rpc}=require("@web/core/network/rpc");const{_t}=require("@web/core/l10n/translation");const ChatWindow=__exports.ChatWindow=class ChatWindow extends Record{static id="thread";static records={};static get(data){return super.get(data);}
static insert(){return super.insert(...arguments);}
thread=Record.one("Thread");autofocus=0;hidden=false;fromMessagingMenu=false;hubAsOpened=Record.one("ChatHub",{onAdd(){this.hubAsFolded=undefined;},onDelete(){if(!this.thread&&!this.hubAsOpened){this.delete();}},});hubAsFolded=Record.one("ChatHub",{onAdd(){this.hubAsOpened=undefined;},});get displayName(){return this.thread?.displayName??_t("New message");}
get isOpen(){return Boolean(this.hubAsOpened);}
async close(options={}){const{escape=false}=options;const chatHub=this.store.chatHub;const indexAsOpened=chatHub.opened.findIndex((w)=>w.eq(this));const thread=this.thread;if(thread){thread.state="closed";}
await this._onClose(options);this.delete();if(escape&&indexAsOpened!==-1&&chatHub.opened.length>0){chatHub.opened[indexAsOpened===0?0:indexAsOpened-1].focus();}}
focus(){this.autofocus++;}
fold(){if(!this.thread){return this.close();}
this.store.chatHub.folded.delete(this);this.store.chatHub.folded.unshift(this);this.thread.state="folded";this.notifyState();}
open({notifyState=true}={}){this.store.chatHub.opened.delete(this);this.store.chatHub.opened.unshift(this);if(this.thread){this.thread.state="open";if(notifyState){this.notifyState();}}
this.focus();}
notifyState(){if(this.store.env.services.ui.isSmall||this.thread?.isTransient||!this.thread?.hasSelfAsMember){return;}
if(this.thread?.model==="discuss.channel"){this.thread.foldStateCount++;return rpc("/discuss/channel/fold",{channel_id:this.thread.id,state:this.thread.state,state_count:this.thread.foldStateCount,},{shadow:true});}}
async _onClose({notifyState=true}={}){if(notifyState){this.notifyState();}}}
ChatWindow.register();return __exports;});;

/* /mail/static/src/core/common/composer.js */
odoo.define('@mail/core/common/composer',['@mail/core/common/attachment_list','@mail/core/common/attachment_uploader_hook','@web/core/dropzone/dropzone_hook','@mail/core/common/picker','@mail/core/common/mail_attachment_dropzone','@mail/core/common/message_confirm_dialog','@mail/core/common/navigable_list','@mail/core/common/suggestion_hook','@mail/utils/common/format','@mail/utils/common/hooks','@mail/utils/common/misc','@web/core/network/rpc','@web/core/utils/misc','@web/core/browser/browser','@web/core/utils/timing','@odoo/owl','@web/core/l10n/translation','@web/core/utils/hooks','@web/views/fields/file_handler','@web/core/utils/strings','@web/core/browser/feature_detection'],function(require){'use strict';let __exports={};const{AttachmentList}=require("@mail/core/common/attachment_list");const{useAttachmentUploader}=require("@mail/core/common/attachment_uploader_hook");const{useCustomDropzone}=require("@web/core/dropzone/dropzone_hook");const{Picker,usePicker}=require("@mail/core/common/picker");const{MailAttachmentDropzone}=require("@mail/core/common/mail_attachment_dropzone");const{MessageConfirmDialog}=require("@mail/core/common/message_confirm_dialog");const{NavigableList}=require("@mail/core/common/navigable_list");const{useSuggestion}=require("@mail/core/common/suggestion_hook");const{prettifyMessageContent}=require("@mail/utils/common/format");const{useSelection}=require("@mail/utils/common/hooks");const{isDragSourceExternalFile}=require("@mail/utils/common/misc");const{rpc}=require("@web/core/network/rpc");const{isEventHandled,markEventHandled}=require("@web/core/utils/misc");const{browser}=require("@web/core/browser/browser");const{useDebounced}=require("@web/core/utils/timing");const{Component,markup,onMounted,onWillUnmount,useChildSubEnv,useEffect,useRef,useState,useExternalListener,toRaw,EventBus,}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{FileUploader}=require("@web/views/fields/file_handler");const{escape,sprintf}=require("@web/core/utils/strings");const{isDisplayStandalone,isIOS,isMobileOS}=require("@web/core/browser/feature_detection");const EDIT_CLICK_TYPE={CANCEL:"cancel",SAVE:"save",};const Composer=__exports.Composer=class Composer extends Component{static components={AttachmentList,Picker,FileUploader,NavigableList,};static defaultProps={autofocus:0,mode:"normal",className:"",sidebar:true,showFullComposer:true,allowUpload:true,};static props=["composer","autofocus?","messageToReplyTo?","onCloseFullComposerCallback?","onDiscardCallback?","onPostCallback?","mode?","placeholder?","dropzoneRef?","messageEdition?","className?","sidebar?","type?","showFullComposer?","allowUpload?",];static template="mail.Composer";setup(){super.setup();this.isMobileOS=isMobileOS();this.isIosPwa=isIOS()&&isDisplayStandalone();this.OR_PRESS_SEND_KEYBIND=markup(_t("or press %(send_keybind)s",{send_keybind:this.sendKeybinds.map((key)=>`<samp>${escape(key)}</samp>`).join(" + "),}));this.store=useState(useService("mail.store"));this.attachmentUploader=useAttachmentUploader(this.thread??this.props.composer.message.thread,{composer:this.props.composer});this.ui=useState(useService("ui"));this.mainActionsRef=useRef("main-actions");this.ref=useRef("textarea");this.fakeTextarea=useRef("fakeTextarea");this.emojiButton=useRef("emoji-button");this.inputContainerRef=useRef("input-container");this.state=useState({active:true,isFullComposerOpen:false,});this.root=useRef("root");this.fullComposerBus=new EventBus();this.selection=useSelection({refName:"textarea",model:this.props.composer.selection,preserveOnClickAwayPredicate:async(ev)=>{await new Promise(setTimeout);return(!this.isEventTrusted(ev)||isEventHandled(ev,"sidebar.openThread")||isEventHandled(ev,"emoji.selectEmoji")||isEventHandled(ev,"Composer.onClickAddEmoji")||isEventHandled(ev,"composer.clickOnAddAttachment")||isEventHandled(ev,"composer.selectSuggestion"));},});this.suggestion=useSuggestion();this.markEventHandled=markEventHandled;this.onDropFile=this.onDropFile.bind(this);this.saveContentDebounced=useDebounced(this.saveContent,5000,{execBeforeUnmount:true,});useExternalListener(window,"beforeunload",this.saveContent.bind(this));if(this.props.dropzoneRef){useCustomDropzone(this.props.dropzoneRef,MailAttachmentDropzone,{extraClass:"o-mail-Composer-dropzone",onDrop:this.onDropFile,},()=>this.allowUpload);}
if(this.props.messageEdition){this.props.messageEdition.composerOfThread=this;}
useChildSubEnv({inComposer:true,});this.picker=usePicker(this.pickerSettings);useEffect((focus)=>{if(focus&&this.ref.el){this.selection.restore();this.ref.el.focus();}},()=>[this.props.autofocus+this.props.composer.autofocus,this.props.placeholder]);useEffect((rThread,cThread)=>{if(cThread&&cThread.eq(rThread)){this.props.composer.autofocus++;}},()=>[this.props.messageToReplyTo?.thread,this.props.composer.thread]);useEffect(()=>{if(this.fakeTextarea.el.scrollHeight){let wasEmpty=false;if(!this.fakeTextarea.el.value){wasEmpty=true;this.fakeTextarea.el.value="0";}
this.ref.el.style.height=this.fakeTextarea.el.scrollHeight+"px";if(wasEmpty){this.fakeTextarea.el.value="";}}
this.saveContentDebounced();},()=>[this.props.composer.text,this.ref.el]);useEffect(()=>{if(!this.props.composer.forceCursorMove){return;}
this.selection.restore();this.props.composer.forceCursorMove=false;},()=>[this.props.composer.forceCursorMove]);onMounted(()=>{this.ref.el.scrollTo({top:0,behavior:"instant"});if(!this.props.composer.text){this.restoreContent();}});onWillUnmount(()=>{this.props.composer.isFocused=false;});useEffect((composerThread,replyToThread)=>{if(replyToThread&&replyToThread!==composerThread){this.props.messageToReplyTo.cancel();}},()=>[this.props.composer.thread,this.props.messageToReplyTo?.thread]);}
get pickerSettings(){return{anchor:this.props.mode==="extended"?undefined:this.mainActionsRef,buttons:[this.emojiButton],close:()=>{if(!this.ui.isSmall){this.props.composer.autofocus++;}},pickers:{emoji:(emoji)=>this.addEmoji(emoji)},position:this.props.mode==="extended"?"bottom-start":this.props.composer.message?"bottom-start":"top-end",fixed:!this.props.composer.message,};}
get placeholder(){if(this.props.placeholder){return this.props.placeholder;}
if(this.thread){if(this.thread.channel_type==="channel"){const threadName=this.thread.displayName;if(this.thread.parent_channel_id){return _t(`Message "%(subChannelName)s"`,{subChannelName:threadName,});}
return _t("Message #%(threadName)s…",{threadName});}
return _t("Message %(thread name)s…",{"thread name":this.thread.displayName});}
return"";}
onClickCancelOrSaveEditText(ev){ev.preventDefault();const composer=toRaw(this.props.composer);if(composer.message&&ev.target.dataset?.type===EDIT_CLICK_TYPE.CANCEL){this.props.onDiscardCallback(ev);}
if(composer.message&&ev.target.dataset?.type===EDIT_CLICK_TYPE.SAVE){this.editMessage(ev);}}
get CANCEL_OR_SAVE_EDIT_TEXT(){if(this.ui.isSmall){return markup(sprintf(escape(_t("%(open_button)s%(icon)s%(open_em)sDiscard editing%(close_em)s%(close_button)s")),{open_button:`<button class='btn px-1 py-0' data-type="${escape(
                            EDIT_CLICK_TYPE.CANCEL
                        )}">`,close_button:"</button>",icon:`<i class='fa fa-times-circle pe-1' data-type="${escape(
                            EDIT_CLICK_TYPE.CANCEL
                        )}"></i>`,open_em:`<em data-type="${escape(EDIT_CLICK_TYPE.CANCEL)}">`,close_em:"</em>",}));}else{const translation1=_t("%(open_samp)sEscape%(close_samp)s %(open_em)sto %(open_cancel)scancel%(close_cancel)s%(close_em)s, %(open_samp)sCTRL-Enter%(close_samp)s %(open_em)sto %(open_save)ssave%(close_save)s%(close_em)s");const translation2=_t("%(open_samp)sEscape%(close_samp)s %(open_em)sto %(open_cancel)scancel%(close_cancel)s%(close_em)s, %(open_samp)sEnter%(close_samp)s %(open_em)sto %(open_save)ssave%(close_save)s%(close_em)s");return markup(sprintf(escape(this.props.mode==="extended"?translation1:translation2),{open_samp:"<samp>",close_samp:"</samp>",open_em:"<em>",close_em:"</em>",open_cancel:`<a role="button" href="#" data-type="${escape(
                        EDIT_CLICK_TYPE.CANCEL
                    )}">`,close_cancel:"</a>",open_save:`<a role="button" href="#" data-type="${escape(
                        EDIT_CLICK_TYPE.SAVE
                    )}">`,close_save:"</a>",}));}}
get SEND_TEXT(){if(this.props.composer.message){return _t("Save editing");}
return this.props.type==="note"?_t("Log"):_t("Send");}
get sendKeybinds(){return this.props.mode==="extended"?[_t("CTRL"),_t("Enter")]:[_t("Enter")];}
get showComposerAvatar(){return!this.compact&&this.props.sidebar;}
get thread(){return this.props.messageToReplyTo?.message?.thread??this.props.composer.thread??null;}
get allowUpload(){return this.props.allowUpload;}
get message(){return this.props.composer.message??null;}
get extraData(){return this.thread.rpcParams;}
get isSendButtonDisabled(){const attachments=this.props.composer.attachments;return(!this.state.active||(!this.props.composer.text&&attachments.length===0)||attachments.some(({uploading})=>Boolean(uploading)));}
get hasSendButtonNonEditing(){return!this.extended&&!this.props.composer.message;}
get hasSuggestions(){return Boolean(this.suggestion?.state.items);}
get navigableListProps(){const props={anchorRef:this.inputContainerRef.el,position:this.env.inChatter?"bottom-fit":"top-fit",onSelect:(ev,option)=>{this.suggestion.insert(option);markEventHandled(ev,"composer.selectSuggestion");},isLoading:!!this.suggestion.search.term&&this.suggestion.state.isFetching,options:[],};if(!this.hasSuggestions){return props;}
const suggestions=this.suggestion.state.items.suggestions;switch(this.suggestion.state.items.type){case"Partner":return{...props,optionTemplate:"mail.Composer.suggestionPartner",options:suggestions.map((suggestion)=>{if(suggestion.isSpecial){return{...suggestion,group:1,optionTemplate:"mail.Composer.suggestionSpecial",classList:"o-mail-Composer-suggestion",};}else{return{label:suggestion.name,partner:suggestion,classList:"o-mail-Composer-suggestion",};}}),};case"Thread":return{...props,optionTemplate:"mail.Composer.suggestionThread",options:suggestions.map((suggestion)=>{return{label:suggestion.parent_channel_id?`${suggestion.parent_channel_id.displayName} > ${suggestion.displayName}`:suggestion.displayName,thread:suggestion,classList:"o-mail-Composer-suggestion",};}),};case"ChannelCommand":return{...props,optionTemplate:"mail.Composer.suggestionChannelCommand",options:suggestions.map((suggestion)=>{return{label:suggestion.name,help:suggestion.help,classList:"o-mail-Composer-suggestion",};}),};case"mail.canned.response":return{...props,autoSelectFirst:false,hint:_t("Tab to select"),optionTemplate:"mail.Composer.suggestionCannedResponse",options:suggestions.map((suggestion)=>{return{cannedResponse:suggestion,source:suggestion.source,label:suggestion.substitution,classList:"o-mail-Composer-suggestion",};}),};default:return props;}}
onDropFile(ev){if(isDragSourceExternalFile(ev.dataTransfer)){for(const file of ev.dataTransfer.files){this.attachmentUploader.uploadFile(file);}}}
onCloseFullComposerCallback(){if(this.props.onCloseFullComposerCallback){this.props.onCloseFullComposerCallback();}else{this.thread?.fetchNewMessages();}}
onPaste(ev){if(!this.allowUpload){return;}
if(!ev.clipboardData?.items){return;}
if(ev.clipboardData.files.length===0){return;}
ev.preventDefault();for(const file of ev.clipboardData.files){this.attachmentUploader.uploadFile(file);}}
onKeydown(ev){const composer=toRaw(this.props.composer);switch(ev.key){case"ArrowUp":if(this.props.messageEdition&&composer.text===""){const messageToEdit=composer.thread.lastEditableMessageOfSelf;if(messageToEdit){this.props.messageEdition.editingMessage=messageToEdit;}}
break;case"Enter":{if(isEventHandled(ev,"NavigableList.select")||!this.state.active){ev.preventDefault();return;}
if(this.isMobileOS||ev.isComposing){return;}
const shouldPost=this.props.mode==="extended"?ev.ctrlKey:!ev.shiftKey;if(!shouldPost){return;}
ev.preventDefault();if(composer.message){this.editMessage();}else{this.sendMessage();}
break;}
case"Escape":if(isEventHandled(ev,"NavigableList.close")){return;}
if(this.props.onDiscardCallback){this.props.onDiscardCallback();markEventHandled(ev,"Composer.discard");}
break;}}
onClickAddAttachment(ev){const composer=toRaw(this.props.composer);markEventHandled(ev,"composer.clickOnAddAttachment");composer.autofocus++;}
async onClickFullComposer(ev){if(this.props.type!=="note"){const newPartners=this.thread.suggestedRecipients.filter((recipient)=>recipient.checked&&!recipient.persona);if(newPartners.length!==0){const recipientEmails=[];const recipientAdditionalValues={};newPartners.forEach((recipient)=>{recipientEmails.push(recipient.email);recipientAdditionalValues[recipient.email]=recipient.create_values||{};});const partners=await rpc("/mail/partner/from_email",{emails:recipientEmails,additional_values:recipientAdditionalValues,});for(const index in partners){const partnerData=partners[index];const persona=this.store.Persona.insert({...partnerData,type:"partner"});const email=recipientEmails[index];const recipient=this.thread.suggestedRecipients.find((recipient)=>recipient.email===email);Object.assign(recipient,{persona});}}}
const attachmentIds=this.props.composer.attachments.map((attachment)=>attachment.id);const body=this.props.composer.text;const validMentions=this.store.getMentionsFromText(body,{mentionedChannels:this.props.composer.mentionedChannels,mentionedPartners:this.props.composer.mentionedPartners,});let default_body=await prettifyMessageContent(body,validMentions);if(!default_body){const composer=toRaw(this.props.composer);composer.emailAddSignature=true;}
default_body=this.formatDefaultBodyForFullComposer(default_body,this.props.composer.emailAddSignature?this.thread.effectiveSelf.signature:"");const context={default_attachment_ids:attachmentIds,default_body,default_email_add_signature:false,default_model:this.thread.model,default_partner_ids:this.props.type==="note"?[]:this.thread.suggestedRecipients.filter((recipient)=>recipient.checked).map((recipient)=>recipient.persona.id),default_res_ids:[this.thread.id],default_subtype_xmlid:this.props.type==="note"?"mail.mt_note":"mail.mt_comment",mail_post_autofollow:this.thread.hasWriteAccess,body_contains_signature_only:!body||body.trim().length===0,};const action={name:this.props.type==="note"?_t("Log note"):_t("Compose Email"),type:"ir.actions.act_window",res_model:"mail.compose.message",view_mode:"form",views:[[false,"form"]],target:"new",context:context,};const options={onClose:(...args)=>{const accidentalDiscard=args.length===0;const isDiscard=accidentalDiscard||args[0]?.special;if(!isDiscard&&this.props.composer.thread.model==="mail.box"){this.notifySendFromMailbox();}
if(accidentalDiscard){this.fullComposerBus.trigger("ACCIDENTAL_DISCARD",{onAccidentalDiscard:(isEmpty)=>{if(!isEmpty){this.saveContent();this.restoreContent();}},});}else{this.clear();}
this.props.messageToReplyTo?.cancel();this.onCloseFullComposerCallback();this.state.isFullComposerOpen=false;this.fullComposerBus=new EventBus();},props:{fullComposerBus:this.fullComposerBus,},};await this.env.services.action.doAction(action,options);this.state.isFullComposerOpen=true;}
formatDefaultBodyForFullComposer(defaultBody,signature=""){if(signature){defaultBody=`${defaultBody}<br>${signature}`;}
return`<div>${defaultBody}</div>`;}
clear(){this.props.composer.clear();browser.localStorage.removeItem(this.props.composer.localId);}
notifySendFromMailbox(){this.env.services.notification.add(_t('Message posted on "%s"',this.thread.displayName),{type:"info",});}
onClickAddEmoji(ev){markEventHandled(ev,"Composer.onClickAddEmoji");}
isEventTrusted(ev){return ev.isTrusted;}
async processMessage(cb){const el=this.ref.el;if(this.props.composer.attachments.some(({uploading})=>uploading)){this.env.services.notification.add(_t("Please wait while the file is uploading."),{type:"warning",});}else if(this.canProcessMessage){if(!this.state.active){return;}
this.state.active=false;await cb(this.props.composer.text);if(this.props.onPostCallback){this.props.onPostCallback();}
this.clear();this.state.active=true;el.focus();}}
get canProcessMessage(){return(this.props.composer.text.trim()||this.props.composer.attachments.length>0||(this.message&&this.message.attachment_ids.length>0));}
async sendMessage(){const composer=toRaw(this.props.composer);if(composer.message){this.editMessage();return;}
await this.processMessage(async(value)=>{await this._sendMessage(value,this.postData,this.extraData);});}
get postData(){const composer=toRaw(this.props.composer);return{attachments:composer.attachments||[],emailAddSignature:composer.emailAddSignature,isNote:this.props.type==="note",mentionedChannels:composer.mentionedChannels||[],mentionedPartners:composer.mentionedPartners||[],cannedResponseIds:composer.cannedResponses.map((c)=>c.id),parentId:this.props.messageToReplyTo?.message?.id,};}
async _sendMessage(value,postData,extraData){const thread=toRaw(this.props.composer.thread);const postThread=toRaw(this.thread);const post=postThread.post.bind(postThread,value,postData,extraData);if(postThread.model==="discuss.channel"){post();}else{await post();}
if(thread.model==="mail.box"){this.notifySendFromMailbox();}
this.suggestion?.clearRawMentions();this.suggestion?.clearCannedResponses();this.props.messageToReplyTo?.cancel();this.props.composer.emailAddSignature=true;}
async editMessage(){const composer=toRaw(this.props.composer);if(!this.askDeleteFromEdit){await this.processMessage(async(value)=>composer.message.edit(value,composer.attachments,{mentionedChannels:composer.mentionedChannels,mentionedPartners:composer.mentionedPartners,}));}else{this.env.services.dialog.add(MessageConfirmDialog,{message:composer.message,onConfirm:()=>this.message.remove({removeFromThread:this.shouldHideFromMessageListOnDelete,}),prompt:_t("Are you sure you want to delete this message?"),},{context:this});}
this.suggestion?.clearRawMentions();}
get askDeleteFromEdit(){const composer=toRaw(this.props.composer);return!composer.text&&composer.message.attachment_ids.length===0;}
addEmoji(str){const composer=toRaw(this.props.composer);const text=composer.text;const firstPart=text.slice(0,composer.selection.start);const secondPart=text.slice(composer.selection.end,text.length);composer.text=firstPart+str+secondPart;this.selection.moveCursor((firstPart+str).length);if(!this.ui.isSmall){composer.autofocus++;}}
onFocusin(ev){ev.stopPropagation();const composer=toRaw(this.props.composer);composer.isFocused=true;composer.thread?.markAsRead();}
onFocusout(ev){if([EDIT_CLICK_TYPE.CANCEL,EDIT_CLICK_TYPE.SAVE].includes(ev.relatedTarget?.dataset?.type)){return;}
this.props.composer.isFocused=false;}
saveContent(){const composer=toRaw(this.props.composer);const saveContentToLocalStorage=(text,emailAddSignature)=>{const config={emailAddSignature,text,};browser.localStorage.setItem(composer.localId,JSON.stringify(config));};if(this.state.isFullComposerOpen){this.fullComposerBus.trigger("SAVE_CONTENT",{onSaveContent:saveContentToLocalStorage,});}else{saveContentToLocalStorage(composer.text,true);}}
restoreContent(){const composer=toRaw(this.props.composer);try{const config=JSON.parse(browser.localStorage.getItem(composer.localId));if(config.text){composer.emailAddSignature=config.emailAddSignature;composer.text=config.text;}}catch{browser.localStorage.removeItem(composer.localId);}}
get shouldHideFromMessageListOnDelete(){return false;}}
return __exports;});;

/* /mail/static/src/core/common/composer_model.js */
odoo.define('@mail/core/common/composer_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{OR,Record}=require("@mail/core/common/record");const Composer=__exports.Composer=class Composer extends Record{static id=OR("thread","message");static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
clear(){this.attachments.length=0;this.text="";Object.assign(this.selection,{start:0,end:0,direction:"none",});}
attachments=Record.many("Attachment");emailAddSignature=true;message=Record.one("Message");mentionedPartners=Record.many("Persona");mentionedChannels=Record.many("Thread");cannedResponses=Record.many("mail.canned.response");text="";thread=Record.one("Thread");selection={start:0,end:0,direction:"none",};forceCursorMove;isFocused=false;autofocus=0;}
Composer.register();return __exports;});;

/* /mail/static/src/core/common/country_flag.js */
odoo.define('@mail/core/common/country_flag',['@odoo/owl'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const CountryFlag=__exports.CountryFlag=class CountryFlag extends Component{static props=["country","class?"];static template="mail.CountryFlag";}
return __exports;});;

/* /mail/static/src/core/common/country_model.js */
odoo.define('@mail/core/common/country_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const Country=__exports.Country=class Country extends Record{static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
code;id;name;get flagUrl(){return`/base/static/img/country_flags/${encodeURIComponent(this.code.toLowerCase())}.png`;}}
Country.register();return __exports;});;

/* /mail/static/src/core/common/date_section.js */
odoo.define('@mail/core/common/date_section',['@odoo/owl','@web/core/browser/feature_detection'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{isMobileOS}=require("@web/core/browser/feature_detection");const DateSection=__exports.DateSection=class DateSection extends Component{static template="mail.DateSection";static props=["date","className?"];get isMobileOS(){return isMobileOS();}}
return __exports;});;

/* /mail/static/src/core/common/discuss_component_registry.js */
odoo.define('@mail/core/common/discuss_component_registry',['@web/core/registry'],function(require){'use strict';let __exports={};const{registry}=require("@web/core/registry");const discussComponentRegistry=__exports.discussComponentRegistry=registry.category("discuss.component");return __exports;});;

/* /mail/static/src/core/common/emoji_picker_mobile.js */
odoo.define('@mail/core/common/emoji_picker_mobile',['@odoo/owl','@web/core/dialog/dialog','@web/core/emoji_picker/emoji_picker'],function(require){'use strict';let __exports={};const{Component,xml}=require("@odoo/owl");const{Dialog}=require("@web/core/dialog/dialog");const{EMOJI_PICKER_PROPS,EmojiPicker}=require("@web/core/emoji_picker/emoji_picker");const EmojiPickerMobile=__exports.EmojiPickerMobile=class EmojiPickerMobile extends Component{static components={Dialog,EmojiPicker};static props=EMOJI_PICKER_PROPS;static template=xml`
        <Dialog size="'lg'" header="false" footer="false" contentClass="'o-discuss-mobileContextMenu d-flex position-absolute bottom-0 rounded-0 h-50 bg-100'">
            <EmojiPicker t-props="props"/>
        </Dialog>
    `;}
return __exports;});;

/* /mail/static/src/core/common/failure_model.js */
odoo.define('@mail/core/common/failure_model',['@mail/core/common/record','@odoo/owl','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const{markRaw}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const Failure=__exports.Failure=class Failure extends Record{static nextId=markRaw({value:1});static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
notifications=Record.many("Notification",{onUpdate(){if(this.notifications.length===0){this.delete();}else{this.store.failures.add(this);}},});get modelName(){return this.notifications?.[0]?.message?.thread?.modelName;}
get resModel(){return this.notifications?.[0]?.message?.thread?.model;}
get resIds(){return new Set([...this.notifications.map((notif)=>notif.message?.thread?.id).filter((id)=>!!id),]);}
lastMessage=Record.one("Message",{compute(){let lastMsg=this.notifications[0]?.message;for(const notification of this.notifications){if(lastMsg?.id<notification.message?.id){lastMsg=notification.message;}}
return lastMsg;},});get type(){return this.notifications?.[0]?.notification_type;}
get status(){return this.notifications?.[0]?.notification_status;}
get iconSrc(){return"/mail/static/src/img/smiley/mailfailure.svg";}
get body(){if(this.notifications.length===1&&this.lastMessage?.thread){return _t("An error occurred when sending an email on “%(record_name)s”",{record_name:this.lastMessage.thread.name,});}
return _t("An error occurred when sending an email");}
get datetime(){return this.lastMessage?.datetime;}}
Failure.register();return __exports;});;

/* /mail/static/src/core/common/follower_model.js */
odoo.define('@mail/core/common/follower_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const Follower=__exports.Follower=class Follower extends Record{static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
thread=Record.one("Thread");id;is_active;partner=Record.one("Persona");get displayName(){return this.partner.name||this.display_name;}
get isEditable(){const hasWriteAccess=this.thread?this.thread.hasWriteAccess:false;return this.partner.eq(this.thread?.effectiveSelf)?this.thread.hasReadAccess:hasWriteAccess;}
async remove(){await this.store.env.services.orm.call(this.thread.model,"message_unsubscribe",[[this.thread.id],[this.partner.id],]);this.delete();}
removeRecipient(){this.thread.recipients.delete(this);}}
Follower.register();return __exports;});;

/* /mail/static/src/core/common/im_status.js */
odoo.define('@mail/core/common/im_status',['@odoo/owl','@mail/discuss/typing/common/typing'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{Typing}=require("@mail/discuss/typing/common/typing");const ImStatus=__exports.ImStatus=class ImStatus extends Component{static props=["persona?","className?","style?","member?","size?"];static template="mail.ImStatus";static defaultProps={className:"",style:"",size:"lg"};static components={Typing};get persona(){return this.props.persona??this.props.member?.persona;}}
return __exports;});;

/* /mail/static/src/core/common/im_status_service_patch.js */
odoo.define('@mail/core/common/im_status_service_patch',['@bus/im_status_service','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{AWAY_DELAY,imStatusService}=require("@bus/im_status_service");const{patch}=require("@web/core/utils/patch");const imStatusServicePatch=__exports.imStatusServicePatch={start(env,services){const{bus_service,presence}=services;const API=super.start(env,services);bus_service.subscribe("bus.bus/im_status_updated",({presence_status,im_status,partner_id,guest_id})=>{const store=env.services["mail.store"];if(!store){return;}
const persona=store.Persona.get({type:partner_id?"partner":"guest",id:partner_id??guest_id,});if(!persona){return;}
persona.debouncedSetImStatus(im_status);if(persona.type!=="guest"||persona.notEq(store.self)){return;}
const isOnline=presence.getInactivityPeriod()<AWAY_DELAY;if((presence_status==="away"&&isOnline)||presence_status==="offline"){this.updateBusPresence();}});return API;},};const unpatchImStatusService=__exports.unpatchImStatusService=patch(imStatusService,imStatusServicePatch);return __exports;});;

/* /mail/static/src/core/common/link_preview.js */
odoo.define('@mail/core/common/link_preview',['@mail/core/common/link_preview_confirm_delete','@odoo/owl','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{LinkPreviewConfirmDelete}=require("@mail/core/common/link_preview_confirm_delete");const{Component}=require("@odoo/owl");const{useService}=require("@web/core/utils/hooks");const LinkPreview=__exports.LinkPreview=class LinkPreview extends Component{static template="mail.LinkPreview";static props=["linkPreview","deletable"];static components={};setup(){super.setup();this.dialogService=useService("dialog");}
onClick(){this.dialogService.add(LinkPreviewConfirmDelete,{linkPreview:this.props.linkPreview,LinkPreview,});}
onImageLoaded(){this.env.onImageLoaded?.();}}
return __exports;});;

/* /mail/static/src/core/common/link_preview_confirm_delete.js */
odoo.define('@mail/core/common/link_preview_confirm_delete',['@web/core/network/rpc','@odoo/owl','@web/core/dialog/dialog','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{rpc}=require("@web/core/network/rpc");const{Component,useState}=require("@odoo/owl");const{Dialog}=require("@web/core/dialog/dialog");const{useService}=require("@web/core/utils/hooks");const LinkPreviewConfirmDelete=__exports.LinkPreviewConfirmDelete=class LinkPreviewConfirmDelete extends Component{static components={Dialog};static props=["linkPreview","close","LinkPreview"];static template="mail.LinkPreviewConfirmDelete";setup(){super.setup();this.store=useState(useService("mail.store"));}
get message(){return this.props.linkPreview.message;}
onClickOk(){rpc("/mail/link_preview/hide",{link_preview_ids:[this.props.linkPreview.id]},{silent:true});this.props.close();}
onClickDeleteAll(){rpc("/mail/link_preview/hide",{link_preview_ids:this.message.linkPreviews.map((lp)=>lp.id)},{silent:true});this.props.close();}
onClickCancel(){this.props.close();}}
return __exports;});;

/* /mail/static/src/core/common/link_preview_list.js */
odoo.define('@mail/core/common/link_preview_list',['@mail/core/common/link_preview','@odoo/owl'],function(require){'use strict';let __exports={};const{LinkPreview}=require("@mail/core/common/link_preview");const{Component}=require("@odoo/owl");const LinkPreviewList=__exports.LinkPreviewList=class LinkPreviewList extends Component{static template="mail.LinkPreviewList";static props=["linkPreviews","deletable?"];static defaultProps={deletable:false,};static components={LinkPreview};}
return __exports;});;

/* /mail/static/src/core/common/link_preview_model.js */
odoo.define('@mail/core/common/link_preview_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const LinkPreview=__exports.LinkPreview=class LinkPreview extends Record{static id="id";static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
id;message=Record.one("Message",{inverse:"linkPreviews"});image_mimetype;og_description;og_image;og_mimetype;og_title;og_type;og_site_name;source_url;get imageUrl(){return this.og_image?this.og_image:this.source_url;}
get isImage(){return Boolean(this.image_mimetype||this.og_mimetype==="image/gif");}
get isVideo(){return Boolean(!this.isImage&&this.og_type&&this.og_type.startsWith("video"));}
get isCard(){return!this.isImage&&!this.isVideo;}}
LinkPreview.register();return __exports;});;

/* /mail/static/src/core/common/mail_attachment_dropzone.js */
odoo.define('@mail/core/common/mail_attachment_dropzone',['@odoo/owl','@web/core/dropzone/dropzone'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{Dropzone}=require("@web/core/dropzone/dropzone");const MailAttachmentDropzone=__exports.MailAttachmentDropzone=class MailAttachmentDropzone extends Component{static template="mail.MailAttachmentDropzone";static components={Dropzone};static props=Dropzone.props;}
return __exports;});;

/* /mail/static/src/core/common/mail_core_common_service.js */
odoo.define('@mail/core/common/mail_core_common_service',['@odoo/owl','@web/core/registry'],function(require){'use strict';let __exports={};const{reactive}=require("@odoo/owl");const{registry}=require("@web/core/registry");const MailCoreCommon=__exports.MailCoreCommon=class MailCoreCommon{constructor(env,services){this.env=env;this.busService=services.bus_service;this.store=services["mail.store"];}
setup(){this.busService.subscribe("ir.attachment/delete",(payload)=>{const{id:attachmentId,message:messageData}=payload;if(messageData){this.store.Message.insert(messageData);}
const attachment=this.store.Attachment.get(attachmentId);attachment?.delete();});this.busService.subscribe("mail.message/delete",(payload,{id:notifId})=>{for(const messageId of payload.message_ids){const message=this.store.Message.get(messageId);if(!message){continue;}
this.env.bus.trigger("mail.message/delete",{message,notifId});message.delete();}});this.busService.subscribe("mail.message/toggle_star",(payload,metadata)=>this._handleNotificationToggleStar(payload,metadata));this.busService.subscribe("res.users.settings",(payload)=>{if(payload){this.store.settings.update(payload);}});this.busService.subscribe("mail.record/insert",(payload)=>{this.store.insert(payload,{html:true});});}
_handleNotificationToggleStar(payload,metadata){const{message_ids:messageIds,starred}=payload;this.store.Message.insert(messageIds.map((id)=>({id,starred})));}}
const mailCoreCommon=__exports.mailCoreCommon={dependencies:["bus_service","mail.store"],start(env,services){const mailCoreCommon=reactive(new MailCoreCommon(env,services));mailCoreCommon.setup();return mailCoreCommon;},};registry.category("services").add("mail.core.common",mailCoreCommon);return __exports;});;

/* /mail/static/src/core/common/mail_popout_service.js */
odoo.define('@mail/core/common/mail_popout_service',['@odoo/owl','@web/core/browser/browser','@web/core/l10n/translation','@web/core/registry','@web/core/templates','@web/core/utils/html'],function(require){'use strict';let __exports={};const{App}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{getTemplate}=require("@web/core/templates");const{setElementContent}=require("@web/core/utils/html");const mailPopoutService=__exports.mailPopoutService={start(env){let externalWindow;let beforeFn=()=>{};let afterFn=()=>{};let app;function reset(){if(externalWindow?.document){setElementContent(externalWindow.document.head,"");externalWindow.document.write(window.document.head.outerHTML);externalWindow.document.body=externalWindow.document.createElement("body");}
if(app){app.destroy();app=null;}}
async function pollClosedWindow(){while(externalWindow){await new Promise((r)=>setTimeout(r,1000));if(externalWindow.closed){externalWindow=null;afterFn();}}}
function addHooks(beforePopout=()=>{},afterPopoutClosed=()=>{}){beforeFn=beforePopout;afterFn=afterPopoutClosed;}
function popout(component,props){if(!externalWindow||externalWindow.closed){beforeFn();externalWindow=browser.open("about:blank","_blank","popup=yes");window.addEventListener("beforeunload",()=>{if(externalWindow&&!externalWindow.closed){externalWindow.close();}});pollClosedWindow();}
reset();app=new App(component,{name:"Popout",env,props,getTemplate,translatableAttributes:["data-tooltip"],translateFn:_t,});app.mount(externalWindow.document.body);return externalWindow;}
return{get externalWindow(){return externalWindow&&externalWindow.closed?null:externalWindow;},popout,reset,addHooks,};},};registry.category("services").add("mail.popout",mailPopoutService);return __exports;});;

/* /mail/static/src/core/common/message.js */
odoo.define('@mail/core/common/message',['@mail/core/common/attachment_list','@mail/core/common/composer','@mail/core/common/im_status','@mail/core/common/link_preview_list','@mail/core/common/message_in_reply','@mail/core/common/message_notification_popover','@mail/core/common/message_reaction_menu','@mail/core/common/message_reactions','@mail/core/common/message_seen_indicator','@mail/core/common/relative_time','@mail/utils/common/format','@web/core/utils/misc','@web/core/utils/render','@odoo/owl','@web/core/action_swiper/action_swiper','@web/core/browser/feature_detection','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_hooks','@web/core/dropdown/dropdown_item','@web/core/l10n/translation','@web/core/popover/popover_hook','@web/core/utils/hooks','@web/core/utils/html','@web/core/utils/urls','@mail/core/common/message_actions','@web/core/browser/cookie','@web/core/utils/strings','@mail/core/common/message_action_menu_mobile','@mail/core/common/discuss_component_registry'],function(require){'use strict';let __exports={};const{AttachmentList}=require("@mail/core/common/attachment_list");const{Composer}=require("@mail/core/common/composer");const{ImStatus}=require("@mail/core/common/im_status");const{LinkPreviewList}=require("@mail/core/common/link_preview_list");const{MessageInReply}=require("@mail/core/common/message_in_reply");const{MessageNotificationPopover}=require("@mail/core/common/message_notification_popover");const{MessageReactionMenu}=require("@mail/core/common/message_reaction_menu");const{MessageReactions}=require("@mail/core/common/message_reactions");const{MessageSeenIndicator}=require("@mail/core/common/message_seen_indicator");const{RelativeTime}=require("@mail/core/common/relative_time");const{htmlToTextContentInline}=require("@mail/utils/common/format");const{isEventHandled,markEventHandled}=require("@web/core/utils/misc");const{renderToElement}=require("@web/core/utils/render");const{Component,onMounted,onPatched,onWillDestroy,onWillUpdateProps,toRaw,useChildSubEnv,useEffect,useRef,useState,}=require("@odoo/owl");const{ActionSwiper}=require("@web/core/action_swiper/action_swiper");const{hasTouch,isMobileOS}=require("@web/core/browser/feature_detection");const{Dropdown}=require("@web/core/dropdown/dropdown");const{useDropdownState}=require("@web/core/dropdown/dropdown_hooks");const{DropdownItem}=require("@web/core/dropdown/dropdown_item");const{_t}=require("@web/core/l10n/translation");const{usePopover}=require("@web/core/popover/popover_hook");const{useService}=require("@web/core/utils/hooks");const{setElementContent}=require("@web/core/utils/html");const{url}=require("@web/core/utils/urls");const{messageActionsRegistry,useMessageActions}=require("@mail/core/common/message_actions");const{cookie}=require("@web/core/browser/cookie");const{escape}=require("@web/core/utils/strings");const{MessageActionMenuMobile}=require("@mail/core/common/message_action_menu_mobile");const{discussComponentRegistry}=require("@mail/core/common/discuss_component_registry");const Message=__exports.Message=class Message extends Component{static SHADOW_LINK_COLOR="#66598f";static SHADOW_HIGHLIGHT_COLOR="#e99d00bf";static SHADOW_LINK_HOVER_COLOR="#564b79";static components={ActionSwiper,AttachmentList,Composer,Dropdown,DropdownItem,LinkPreviewList,MessageInReply,MessageReactions,MessageSeenIndicator,ImStatus,Popover:MessageNotificationPopover,RelativeTime,};static defaultProps={hasActions:true,isInChatWindow:false,showDates:true,};static props=["asCard?","registerMessageRef?","hasActions?","isInChatWindow?","onParentMessageClick?","message","messageEdition?","messageToReplyTo?","previousMessage?","squashed?","thread?","messageSearch?","className?","showDates?","isFirstMessage?",];static template="mail.Message";setup(){super.setup();this.escape=escape;this.popover=usePopover(this.constructor.components.Popover,{position:"top"});this.state=useState({isEditing:false,isHovered:false,isClicked:false,expandOptions:false,emailHeaderOpen:false,actionMenuMobileOpen:false,});this.shadowRoot;this.root=useRef("root");onWillUpdateProps((nextProps)=>{this.props.registerMessageRef?.(this.props.message,null);});onMounted(()=>this.props.registerMessageRef?.(this.props.message,this.root));onPatched(()=>this.props.registerMessageRef?.(this.props.message,this.root));onWillDestroy(()=>this.props.registerMessageRef?.(this.props.message,null));this.hasTouch=hasTouch;this.messageBody=useRef("body");this.messageActions=useMessageActions();this.store=useState(useService("mail.store"));this.shadowBody=useRef("shadowBody");this.dialog=useService("dialog");this.ui=useState(useService("ui"));this.openReactionMenu=this.openReactionMenu.bind(this);this.optionsDropdown=useDropdownState();useChildSubEnv({message:this.props.message,alignedRight:this.isAlignedRight,});useEffect((editingMessage)=>{if(this.props.message.eq(editingMessage)){messageActionsRegistry.get("edit").onClick(this);}},()=>[this.props.messageEdition?.editingMessage]);onMounted(()=>{if(this.shadowBody.el){this.shadowRoot=this.shadowBody.el.attachShadow({mode:"open"});const color=cookie.get("color_scheme")==="dark"?"white":"black";const shadowStyle=document.createElement("style");shadowStyle.textContent=`
                    * {
                        background-color: transparent !important;
                        color: ${color} !important;
                    }
                    a, a * {
                        color: ${this.constructor.SHADOW_LINK_COLOR} !important;
                    }
                    a:hover, a *:hover {
                        color: ${this.constructor.SHADOW_LINK_HOVER_COLOR} !important;
                    }
                    .o-mail-Message-searchHighlight {
                        background: ${this.constructor.SHADOW_HIGHLIGHT_COLOR} !important;
                    }
                `;if(cookie.get("color_scheme")==="dark"){this.shadowRoot.appendChild(shadowStyle);}}});useEffect(()=>{if(this.messageBody.el){this.prepareMessageBody(this.messageBody.el);}
if(this.shadowBody.el){const bodyEl=document.createElement("span");setElementContent(bodyEl,this.message.showTranslation?this.message.translationValue:this.props.messageSearch?.highlight(this.message.body)??this.message.body);this.prepareMessageBody(bodyEl);this.shadowRoot.appendChild(bodyEl);return()=>{this.shadowRoot.removeChild(bodyEl);};}},()=>[this.message.showTranslation,this.message.translationValue,this.props.messageSearch?.searchTerm,this.message.body,this.message.composer,]);}
get attClass(){return{[this.props.className]:true,"o-card p-2 mt-2 border border-secondary":this.props.asCard,"pt-1":!this.props.asCard,"o-selfAuthored":this.message.isSelfAuthored&&!this.env.messageCard,"o-selected":this.props.messageToReplyTo?.isSelected(this.props.thread,this.props.message),"o-squashed":this.props.squashed,"mt-1":!this.props.squashed&&this.props.thread&&!this.env.messageCard&&!this.props.asCard,"px-2":this.props.isInChatWindow,"opacity-50":this.props.messageToReplyTo?.isNotSelected(this.props.thread,this.props.message),"o-actionMenuMobileOpen":this.state.actionMenuMobileOpen,"o-editing":this.state.isEditing,};}
get authorAvatarAttClass(){return{o_object_fit_contain:this.props.message.author?.is_company,o_object_fit_cover:!this.props.message.author?.is_company,};}
get authorName(){if(this.message.author){return this.message.author.name;}
return this.message.email_from||_t("Unnamed");}
get authorAvatarUrl(){if(this.message.message_type&&this.message.message_type.includes("email")&&!["partner","guest"].includes(this.message.author?.type)){return url("/mail/static/src/img/email_icon.png");}
if(this.message.author){return this.message.author.avatarUrl;}
return this.store.DEFAULT_AVATAR;}
get expandText(){return _t("Expand");}
get message(){return this.props.message;}
get quickActionCount(){return this.env.inChatter?3:this.env.inChatWindow?2:4;}
get showSeenIndicator(){return this.props.message.isSelfAuthored&&this.props.thread?.hasSeenFeature;}
get showSubtypeDescription(){return(this.message.subtype_description&&this.message.subtype_description.toLowerCase()!==htmlToTextContentInline(this.message.body||"").toLowerCase());}
get messageTypeText(){if(this.props.message.message_type==="notification"){return _t("System notification");}
if(this.props.message.message_type==="auto_comment"){return _t("Automated message");}
if(!this.props.message.is_discussion&&this.props.message.message_type!=="user_notification"){return _t("Note");}
return _t("Message");}
get isActive(){return(this.state.isHovered||this.state.isClicked||this.emojiPicker?.isOpen||this.optionsDropdown.isOpen);}
get isAlignedRight(){return Boolean(this.env.inChatWindow&&this.props.message.isSelfAuthored);}
get isMobileOS(){return isMobileOS();}
get isPersistentMessageFromAnotherThread(){return!this.isOriginThread&&!this.message.is_transient&&this.message.thread;}
get isOriginThread(){if(!this.props.thread){return false;}
return this.props.thread.eq(this.message.thread);}
get translatedFromText(){return _t("(Translated from: %(language)s)",{language:this.message.translationSource});}
get translationFailureText(){return _t("(Translation Failure: %(error)s)",{error:this.message.translationErrors});}
onMouseenter(){this.state.isHovered=true;}
onMouseleave(){this.state.isHovered=false;this.state.isClicked=null;}
get shouldDisplayAuthorName(){if(!this.env.inChatWindow){return true;}
if(this.message.isSelfAuthored){return false;}
if(this.props.thread.channel_type==="chat"){return false;}
return true;}
async onClickAttachmentUnlink(attachment){await toRaw(attachment).remove();}
async onClick(ev){if(this.store.handleClickOnLink(ev,this.props.thread)){return;}
if(!isEventHandled(ev,"Message.ClickAuthor")&&!isEventHandled(ev,"Message.ClickFailure")){if(this.state.isClicked){this.state.isClicked=false;}else{this.state.isClicked=true;document.body.addEventListener("click",()=>{this.state.isClicked=false;},{capture:true,once:true});}}}
async onClickNotificationMessage(ev){this.store.handleClickOnLink(ev,this.props.thread);const{oeType,oeId}=ev.target.dataset;if(oeType==="highlight"){await this.env.messageHighlight?.highlightMessage(this.store.Message.insert({id:Number(oeId),res_id:this.props.thread.id,model:this.props.thread.model,thread:this.props.thread,}),this.props.thread);}}
prepareMessageBody(bodyEl){if(!bodyEl){return;}
const editedEl=bodyEl.querySelector(".o-mail-Message-edited");editedEl?.replaceChildren(renderToElement("mail.Message.edited"));const linkEls=bodyEl.querySelectorAll(".o_channel_redirect");for(const linkEl of linkEls){const text=linkEl.textContent.substring(1);const icon=linkEl.classList.contains("o_channel_redirect_asThread")?"fa fa-comments-o":"fa fa-hashtag";const iconEl=renderToElement("mail.Message.mentionedChannelIcon",{icon});linkEl.replaceChildren(iconEl);linkEl.insertAdjacentText("beforeend",` ${text}`);}}
getAuthorAttClass(){return{"opacity-50":this.message.isPending};}
getAvatarContainerAttClass(){return{"opacity-50":this.message.isPending,"o-inChatWindow":this.env.inChatWindow,};}
exitEditMode(){const message=toRaw(this.props.message);this.props.messageEdition?.exitEditMode();message.composer=undefined;this.state.isEditing=false;}
onClickNotification(ev){const message=toRaw(this.message);if(message.failureNotifications.length>0){this.onClickFailure(ev);}else{this.popover.open(ev.target,{message});}}
onClickFailure(ev){const message=toRaw(this.message);markEventHandled(ev,"Message.ClickFailure");this.env.services.action.doAction("mail.mail_resend_message_action",{additionalContext:{mail_message_to_resend:message.id,},});}
openMobileActions(ev){if(!isMobileOS()){return;}
ev?.stopPropagation();this.state.actionMenuMobileOpen=true;this.dialog.add(MessageActionMenuMobile,{message:this.props.message,thread:this.props.thread,isFirstMessage:this.props.isFirstMessage,messageToReplyTo:this.props.messageToReplyTo,openReactionMenu:()=>this.openReactionMenu(),state:this.state,},{context:this,onClose:()=>(this.state.actionMenuMobileOpen=false)});}
openReactionMenu(reaction){const message=toRaw(this.props.message);this.dialog.add(MessageReactionMenu,{message,initialReaction:reaction},{context:this});}
async onClickToggleTranslation(){toRaw(this.props.message).onClickToggleTranslation();}
get shouldHideFromMessageListOnDelete(){return false;}}
discussComponentRegistry.add("Message",Message);return __exports;});;

/* /mail/static/src/core/common/message_action_menu_mobile.js */
odoo.define('@mail/core/common/message_action_menu_mobile',['@odoo/owl','@web/core/dialog/dialog','@mail/core/common/message_actions','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{Component,onMounted,onWillUnmount,useState}=require("@odoo/owl");const{Dialog}=require("@web/core/dialog/dialog");const{useMessageActions}=require("@mail/core/common/message_actions");const{useChildRef,useService}=require("@web/core/utils/hooks");const MessageActionMenuMobile=__exports.MessageActionMenuMobile=class MessageActionMenuMobile extends Component{static components={Dialog};static props=["message","close?","thread?","isFirstMessage?","messageToReplyTo?","openReactionMenu?","state",];static template="mail.MessageActionMenuMobile";setup(){super.setup();this.store=useState(useService("mail.store"));this.modalRef=useChildRef();this.messageActions=useMessageActions();this.onClickModal=this.onClickModal.bind(this);onMounted(()=>{this.modalRef.el.addEventListener("click",this.onClickModal);});onWillUnmount(()=>{this.modalRef.el.removeEventListener("click",this.onClickModal);});}
onClickModal(){this.props.close?.();}
get message(){return this.props.message;}
get state(){return this.props.state;}
async onClickAction(action){const success=await action.onClick();if(action.mobileCloseAfterClick&&(success||success===undefined)){this.props.close?.();}}
openReactionMenu(){return this.props.openReactionMenu?.();}}
return __exports;});;

/* /mail/static/src/core/common/message_actions.js */
odoo.define('@mail/core/common/message_actions',['@odoo/owl','@web/core/l10n/translation','@web/core/network/download','@web/core/registry','@mail/core/common/message_reaction_button','@web/core/utils/hooks','@mail/core/common/discuss_component_registry','@web/core/utils/concurrency','@web/core/emoji_picker/emoji_picker','@web/core/dialog/dialog','@mail/utils/common/hooks','@mail/utils/common/format'],function(require){'use strict';let __exports={};const{Component,toRaw,useComponent,useState,xml}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const{download}=require("@web/core/network/download");const{registry}=require("@web/core/registry");const{MessageReactionButton}=require("@mail/core/common/message_reaction_button");const{useService}=require("@web/core/utils/hooks");const{discussComponentRegistry}=require("@mail/core/common/discuss_component_registry");const{Deferred}=require("@web/core/utils/concurrency");const{EMOJI_PICKER_PROPS,EmojiPicker}=require("@web/core/emoji_picker/emoji_picker");const{Dialog}=require("@web/core/dialog/dialog");const{onExternalClick}=require("@mail/utils/common/hooks");const{convertBrToLineBreak}=require("@mail/utils/common/format");const{DateTime}=luxon;const messageActionsRegistry=__exports.messageActionsRegistry=registry.category("mail.message/actions");class EmojiPickerMobile extends Component{static components={Dialog,EmojiPicker};static props=[...EMOJI_PICKER_PROPS,"onClose?"];static template=xml`
        <Dialog size="'lg'" header="false" footer="false" contentClass="'o-discuss-mobileContextMenu d-flex position-absolute bottom-0 rounded-0 h-50 bg-100'">
            <div t-ref="root">
                <EmojiPicker t-props="emojiPickerProps"/>
            </div>
        </Dialog>
    `;get emojiPickerProps(){return{...this.props,onSelect:(...args)=>{this.props.onSelect(...args);this.props.close?.();},};}
setup(){super.setup();onExternalClick("root",()=>this.props.close?.());}}
messageActionsRegistry.add("reaction",{callComponent:MessageReactionButton,props:(component)=>({message:component.props.message,action:messageActionsRegistry.get("reaction"),}),condition:(component)=>component.props.message.canAddReaction(component.props.thread),icon:"oi oi-smile-add",title:_t("Add a Reaction"),onClick:async(component)=>{const def=new Deferred();component.dialog.add(EmojiPickerMobile,{onSelect:(emoji)=>{const reaction=component.props.message.reactions.find(({content,personas})=>content===emoji&&component.props.thread.effectiveSelf.in(personas));if(!reaction){component.props.message.react(emoji);}
def.resolve(true);},},{context:component,onClose:()=>def.resolve(false)});return def;},sequence:10,}).add("reply-to",{condition:(component)=>component.props.message.canReplyTo(component.props.thread),icon:"fa fa-reply",title:_t("Reply"),onClick:(component)=>{const message=toRaw(component.props.message);const thread=toRaw(component.props.thread);component.props.messageToReplyTo.toggle(thread,message);},sequence:(component)=>(component.props.thread?.eq(component.store.inbox)?55:20),}).add("toggle-star",{condition:(component)=>component.props.message.canToggleStar,icon:(component)=>component.props.message.starred?"fa fa-star o-mail-Message-starred":"fa fa-star-o",title:_t("Mark as Todo"),onClick:(component)=>component.props.message.toggleStar(),sequence:30,mobileCloseAfterClick:false,}).add("mark-as-read",{condition:(component)=>component.props.thread?.eq(component.store.inbox),icon:"fa fa-check",title:_t("Mark as Read"),onClick:(component)=>component.props.message.setDone(),sequence:40,}).add("reactions",{condition:(component)=>component.message.reactions.length,icon:"fa fa-smile-o",title:_t("View Reactions"),onClick:(component)=>component.openReactionMenu(),sequence:50,dropdown:true,}).add("unfollow",{condition:(component)=>component.props.message.canUnfollow(component.props.thread),icon:"fa fa-user-times",title:_t("Unfollow"),onClick:(component)=>component.props.message.unfollow(),sequence:60,}).add("mark-as-unread",{condition:(component)=>component.props.thread?.model==="discuss.channel"&&component.store.self.type==="partner"&&component.props.message.persistent,icon:"fa fa-eye-slash",title:_t("Mark as Unread"),onClick:(component)=>component.props.message.onClickMarkAsUnread(component.props.thread),sequence:70,}).add("edit",{condition:(component)=>component.props.message.editable,icon:"fa fa-pencil",title:_t("Edit"),onClick:(component)=>{const message=toRaw(component.props.message);const text=convertBrToLineBreak(message.body);message.composer={mentionedPartners:message.recipients,text,selection:{start:text.length,end:text.length,direction:"none",},};component.state.isEditing=true;},sequence:80,}).add("delete",{condition:(component)=>component.props.message.editable,btnClass:"text-danger",icon:"fa fa-trash",title:_t("Delete"),onClick:async(component)=>{const message=toRaw(component.message);const def=new Deferred();component.dialog.add(discussComponentRegistry.get("MessageConfirmDialog"),{message,prompt:_t("Are you sure you want to delete this message?"),onConfirm:()=>{def.resolve(true);message.remove({removeFromThread:component.shouldHideFromMessageListOnDelete,});},},{context:component,onClose:()=>def.resolve(false)});return def;},setup:()=>{const component=useComponent();component.dialog=useService("dialog");},sequence:90,}).add("download_files",{condition:(component)=>component.message.attachment_ids.length>1&&component.store.self.isInternalUser,icon:"fa fa-download",title:_t("Download Files"),onClick:(component)=>download({data:{file_ids:component.message.attachment_ids.map((rec)=>rec.id),zip_name:`attachments_${DateTime.local().toFormat("HHmmddMMyyyy")}.zip`,},url:"/mail/attachment/zip",}),sequence:55,}).add("toggle-translation",{condition:(component)=>component.props.message.isTranslatable(component.message.thread),icon:(component)=>`fa fa-language ${
                component.message.showTranslation ? "o-mail-Message-translated" : ""
            }`,title:(component)=>(component.message.showTranslation?_t("Revert"):_t("Translate")),onClick:(component)=>component.message.onClickToggleTranslation(),sequence:100,}).add("copy-link",{condition:(component)=>component.message.message_type&&component.message.message_type!=="user_notification"&&(!component.props.thread.access_token||component.props.thread.hasReadAccess),icon:"fa fa-link",title:_t("Copy Link"),onClick:(component)=>component.message.copyLink(),sequence:110,});function transformAction(component,id,action){return{get btnClass(){return typeof action.btnClass==="function"?action.btnClass(component):action.btnClass;},component:action.component,id,mobileCloseAfterClick:action.mobileCloseAfterClick??true,get condition(){return action.condition(component);},get icon(){return typeof action.icon==="function"?action.icon(component):action.icon;},get title(){return typeof action.title==="function"?action.title(component):action.title;},callComponent:action.callComponent,get props(){return action.props(component);},onClick(){return action.onClick?.(component);},get sequence(){return typeof action.sequence==="function"?action.sequence(component):action.sequence;},setup:action.setup,};}
__exports.useMessageActions=useMessageActions;function useMessageActions(){const component=useComponent();const transformedActions=messageActionsRegistry.getEntries().map(([id,action])=>transformAction(component,id,action));for(const action of transformedActions){if(action.setup){action.setup(action);}}
const state=useState({get actions(){const actions=transformedActions.filter((action)=>action.condition).sort((a1,a2)=>a1.sequence-a2.sequence);if(actions.length>0){actions.at(0).isFirst=true;actions.at(-1).isLast=true;}
return actions;},});return state;}
return __exports;});;

/* /mail/static/src/core/common/message_card_list.js */
odoo.define('@mail/core/common/message_card_list',['@mail/core/common/message','@mail/utils/common/hooks','@odoo/owl','@web/core/l10n/translation','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{Message}=require("@mail/core/common/message");const{useVisible}=require("@mail/utils/common/hooks");const{Component,useState,useSubEnv}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const MessageCardList=__exports.MessageCardList=class MessageCardList extends Component{static components={Message};static props=["emptyText?","messages","messageSearch?","loadMore?","mode","onClickJump?","onLoadMoreVisible?","showEmpty?","thread",];static template="mail.MessageCardList";setup(){super.setup();this.store=useState(useService("mail.store"));this.ui=useState(useService("ui"));useSubEnv({messageCard:true});useVisible("load-more",(isVisible)=>{if(isVisible){this.props.onLoadMoreVisible?.();}});}
async onClickJump(message){this.props.onClickJump?.();if(this.ui.isSmall||this.env.inChatWindow){this.env.pinMenu?.close();this.env.searchMenu?.close();}
await new Promise((resolve)=>setTimeout(()=>requestAnimationFrame(resolve)));await this.env.messageHighlight?.highlightMessage(message,this.props.thread);}
get emptyText(){return this.props.emptyText??_t("No messages found");}}
return __exports;});;

/* /mail/static/src/core/common/message_confirm_dialog.js */
odoo.define('@mail/core/common/message_confirm_dialog',['@odoo/owl','@web/core/dialog/dialog','@web/core/l10n/translation','@mail/core/common/discuss_component_registry'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{Dialog}=require("@web/core/dialog/dialog");const{_t}=require("@web/core/l10n/translation");const{discussComponentRegistry}=require("@mail/core/common/discuss_component_registry");const MessageConfirmDialog=__exports.MessageConfirmDialog=class MessageConfirmDialog extends Component{static components={Dialog};static props=["close","confirmColor?","confirmText?","message","prompt","size?","title?","onConfirm",];static defaultProps={confirmColor:"btn-primary",confirmText:_t("Confirm"),size:"xl",title:_t("Confirmation"),};static template="mail.MessageConfirmDialog";get messageComponent(){return discussComponentRegistry.get("Message");}
onClickConfirm(){this.props.onConfirm();this.props.close();}}
discussComponentRegistry.add("MessageConfirmDialog",MessageConfirmDialog);return __exports;});;

/* /mail/static/src/core/common/message_in_reply.js */
odoo.define('@mail/core/common/message_in_reply',['@odoo/owl','@web/core/utils/hooks','@web/core/utils/urls'],function(require){'use strict';let __exports={};const{Component,useState}=require("@odoo/owl");const{useService}=require("@web/core/utils/hooks");const{url}=require("@web/core/utils/urls");const MessageInReply=__exports.MessageInReply=class MessageInReply extends Component{static props=["class?","message","onClick?"];static defaultProps={class:""};static template="mail.MessageInReply";setup(){super.setup();this.store=useState(useService("mail.store"));}
get authorAvatarUrl(){if(this.props.message.message_type&&this.props.message.message_type.includes("email")&&!["partner","guest"].includes(this.props.message.author?.type)){return url("/mail/static/src/img/email_icon.png");}
if(this.props.message.parentMessage.author){return this.props.message.parentMessage.author.avatarUrl;}
return this.store.DEFAULT_AVATAR;}}
return __exports;});;

/* /mail/static/src/core/common/message_model.js */
odoo.define('@mail/core/common/message_model',['@mail/core/common/record','@mail/utils/common/format','@mail/utils/common/html','@odoo/owl','@web/core/browser/browser','@web/core/browser/router','@web/core/l10n/translation','@web/core/network/rpc','@web/core/user','@web/core/utils/html','@web/core/utils/urls'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const{EMOJI_REGEX,convertBrToLineBreak,htmlToTextContentInline,prettifyMessageContent,}=require("@mail/utils/common/format");const{createDocumentFragmentFromContent}=require("@mail/utils/common/html");const{markup,toRaw}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{stateToUrl}=require("@web/core/browser/router");const{_t}=require("@web/core/l10n/translation");const{rpc}=require("@web/core/network/rpc");const{user}=require("@web/core/user");const{setElementContent}=require("@web/core/utils/html");const{url}=require("@web/core/utils/urls");const{DateTime}=luxon;const Message=__exports.Message=class Message extends Record{static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
update(data){super.update(data);if(this.isNotification&&!this.notificationType){const htmlBody=createDocumentFragmentFromContent(this.body);this.notificationType=htmlBody.querySelector(".o_mail_notification")?.dataset.oeType;}}
attachment_ids=Record.many("Attachment",{inverse:"message"});author=Record.one("Persona");body=Record.attr("",{html:true});composer=Record.one("Composer",{inverse:"message",onDelete:(r)=>r.delete()});date=Record.attr(undefined,{type:"datetime"});default_subject;edited=Record.attr(false,{compute(){return Boolean(createDocumentFragmentFromContent(this.body).querySelector(".o-mail-Message-edited"));},});hasEveryoneSeen=Record.attr(false,{compute(){return this.thread?.membersThatCanSeen.every((m)=>m.hasSeen(this));},});isMessagePreviousToLastSelfMessageSeenByEveryone=Record.attr(false,{compute(){if(!this.thread?.lastSelfMessageSeenByEveryone){return false;}
return this.id<this.thread.lastSelfMessageSeenByEveryone.id;},});isReadBySelf=Record.attr(false,{compute(){return(this.thread?.selfMember?.seen_message_id?.id>=this.id&&this.thread?.selfMember?.new_message_separator>this.id);},});hasSomeoneSeen=Record.attr(false,{compute(){return this.thread?.membersThatCanSeen.filter(({persona})=>!persona.eq(this.author)).some((m)=>m.hasSeen(this));},});hasSomeoneFetched=Record.attr(false,{compute(){if(!this.thread){return false;}
const otherFetched=this.thread.channelMembers.filter((m)=>m.persona.notEq(this.author)&&m.fetched_message_id?.id>=this.id);return otherFetched.length>0;},});hasLink=Record.attr(false,{compute(){if(this.isBodyEmpty){return false;}
const div=document.createElement("div");setElementContent(div,this.body);return Boolean(div.querySelector("a:not([data-oe-model])"));},});hasMailNotificationSummary=Record.attr(false,{compute(){return Boolean(createDocumentFragmentFromContent(this.body).querySelector('[summary="o_mail_notification"]'));},});id;is_discussion;is_note;is_transient;linkPreviews=Record.many("LinkPreview",{inverse:"message",onDelete:(r)=>r.delete()});parentMessage=Record.one("Message");postFailRedo=undefined;reactions=Record.many("MessageReactions",{inverse:"message",sort:(r1,r2)=>r1.sequence-r2.sequence,});notifications=Record.many("Notification",{inverse:"message"});recipients=Record.many("Persona");thread=Record.one("Thread");threadAsNeedaction=Record.one("Thread",{compute(){if(this.needaction){return this.thread;}},});threadAsNewest=Record.one("Thread");scheduledDatetime=Record.attr(undefined,{type:"datetime"});onlyEmojis=Record.attr(false,{compute(){const div=document.createElement("div");setElementContent(div,this.body);const bodyWithoutTags=div.textContent;const withoutEmojis=bodyWithoutTags.replace(EMOJI_REGEX,"");return bodyWithoutTags.length>0&&withoutEmojis.trim().length===0;},});subject;subtype_description;threadAsFirstUnread=Record.one("Thread",{inverse:"firstUnreadMessage"});trackingValues=[];translationValue;translationSource;translationErrors;message_type;notificationType;create_date=Record.attr(undefined,{type:"datetime"});write_date=Record.attr(undefined,{type:"datetime"});needaction;starred=false;showTranslation=false;get allowsEdition(){return this.store.self.isAdmin||this.isSelfAuthored;}
get bubbleColor(){if(!this.isSelfAuthored&&!this.is_note&&!this.isHighlightedFromMention){return"blue";}
if(this.isSelfAuthored&&!this.is_note&&!this.isHighlightedFromMention){return"green";}
if(this.isHighlightedFromMention){return"orange";}
return undefined;}
get editable(){if(!this.allowsEdition){return false;}
return this.message_type==="comment";}
get dateDay(){let dateDay=this.datetime.toLocaleString(DateTime.DATE_MED);if(dateDay===DateTime.now().toLocaleString(DateTime.DATE_MED)){dateDay=_t("Today");}
return dateDay;}
get dateSimple(){return this.datetime.toLocaleString(DateTime.TIME_SIMPLE,{locale:user.lang,}).replace(" "," ");}
get dateSimpleWithDay(){const userLocale={locale:user.lang};if(this.datetime.hasSame(DateTime.now(),"day")){return _t("Today at %(time)s",{time:this.datetime.toLocaleString(DateTime.TIME_SIMPLE,userLocale),});}
if(this.datetime.hasSame(DateTime.now().minus({day:1}),"day")){return _t("Yesterday at %(time)s",{time:this.datetime.toLocaleString(DateTime.TIME_SIMPLE,userLocale),});}
if(this.datetime?.year===DateTime.now().year){return this.datetime.toLocaleString({...DateTime.DATETIME_MED,year:undefined},userLocale);}
return this.datetime.toLocaleString({...DateTime.DATETIME_MED},userLocale);}
get datetime(){return this.date||DateTime.now();}
get effectiveSelf(){return this.thread?.effectiveSelf??this.store.self;}
get selves(){return this.thread?.selves??[this.store.self];}
get datetimeShort(){return this.datetime.toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS);}
get isSelfMentioned(){return this.effectiveSelf.in(this.recipients);}
get isHighlightedFromMention(){return this.isSelfMentioned&&this.thread?.model==="discuss.channel";}
isSelfAuthored=Record.attr(false,{compute(){if(!this.author){return false;}
return this.author.eq(this.effectiveSelf);},});isPending=false;get hasActions(){return!this.is_transient;}
get isNotification(){return this.message_type==="notification"&&this.thread?.model==="discuss.channel";}
get isSubjectSimilarToThreadName(){if(!this.subject||!this.thread||!this.thread.name){return false;}
const regexPrefix=/^((re|fw|fwd)\s*:\s*)*/i;const cleanedThreadName=this.thread.name.replace(regexPrefix,"");const cleanedSubject=this.subject.replace(regexPrefix,"");return cleanedSubject===cleanedThreadName;}
get isSubjectDefault(){const name=this.thread?.name;const threadName=name?name.trim().toLowerCase():"";const defaultSubject=this.default_subject?this.default_subject.toLowerCase():"";const candidates=new Set([defaultSubject,threadName]);return candidates.has(this.subject?.toLowerCase());}
get persistent(){return Number.isInteger(this.id);}
get resUrl(){return url(stateToUrl({model:this.thread.model,resId:this.thread.id}));}
isTranslatable(thread){return(!this.isBodyEmpty&&!this.hasMailNotificationSummary&&this.store.hasMessageTranslationFeature&&!["discuss.channel","mail.box"].includes(thread?.model));}
get hasTextContent(){return!this.isBodyEmpty;}
isEmpty=Record.attr(false,{compute(){return this.computeIsEmpty();},});isBodyEmpty=Record.attr(undefined,{compute(){return(!this.body||["","<p></p>","<p><br></p>","<p><br/></p>","<div></div>","<div><br></div>","<div><br/></div>",].includes(this.body.replace('<span class="o-mail-Message-edited"></span>',"").replace(/\s/g,"")));},});computeIsEmpty(){return(this.isBodyEmpty&&this.attachment_ids.length===0&&this.trackingValues.length===0&&!this.subtype_description);}
get linkPreviewSquash(){return(this.store.hasLinkPreviewFeature&&this.body&&this.body.startsWith("<a")&&this.body.endsWith("/a>")&&this.body.match(/<\/a>/im)?.length===1&&this.linkPreviews.length===1&&this.linkPreviews[0].isImage);}
get inlineBody(){if(!this.body){return"";}
return htmlToTextContentInline(this.body);}
get notificationIcon(){switch(this.notificationType){case"pin":return"fa fa-thumb-tack";}
return null;}
get failureNotifications(){return this.notifications.filter((notification)=>notification.isFailure);}
get scheduledDateSimple(){return this.scheduledDatetime.toLocaleString(DateTime.TIME_SIMPLE,{locale:user.lang,});}
get canToggleStar(){return Boolean(!this.is_transient&&this.thread&&this.store.self.type==="partner"&&this.store.self.isInternalUser&&this.persistent);}
canAddReaction(thread){return Boolean(!this.is_transient&&this.thread?.can_react);}
canReplyTo(thread){return(["discuss.channel","mail.box"].includes(thread.model)&&this.message_type!=="user_notification");}
canUnfollow(thread){return Boolean(this.thread?.selfFollower&&thread?.model==="mail.box");}
async copyLink(){let notification=_t("Message Link Copied!");let type="info";try{await browser.navigator.clipboard.writeText(url(`/mail/message/${this.id}`));}catch{notification=_t("Message Link Copy Failed (Permission denied?)!");type="danger";}
this.store.env.services.notification.add(notification,{type});}
async edit(body,attachments=[],{mentionedChannels=[],mentionedPartners=[]}={}){if(convertBrToLineBreak(this.body)===body&&attachments.length===0){return;}
const validMentions=this.store.getMentionsFromText(body,{mentionedChannels,mentionedPartners,});const data=await rpc("/mail/message/update_content",{attachment_ids:attachments.concat(this.attachment_ids).map((attachment)=>attachment.id),attachment_tokens:attachments.concat(this.attachment_ids).map((attachment)=>attachment.access_token),body:await prettifyMessageContent(body,validMentions),message_id:this.id,partner_ids:validMentions?.partners?.map((partner)=>partner.id),...this.thread.rpcParams,});this.store.insert(data,{html:true});if(this.hasLink&&this.store.hasLinkPreviewFeature){rpc("/mail/link_preview",{message_id:this.id},{silent:true});}
return data;}
async onClickToggleTranslation(){if(!this.translationValue){const{error,lang_name,body}=await rpc("/mail/message/translate",{message_id:this.id,});this.translationValue=body&&markup(body);this.translationSource=lang_name;this.translationErrors=error;}
this.showTranslation=!this.showTranslation&&Boolean(this.translationValue);}
async react(content){this.store.insert(await rpc("/mail/message/reaction",{action:"add",content,message_id:this.id,...this.thread.rpcParams,},{silent:true}));}
async remove({removeFromThread=false}={}){const data=await rpc("/mail/message/update_content",this.removeParams);this.store.insert(data,{html:true});if(this.thread&&removeFromThread){this.thread.messages=this.thread.messages.filter((message)=>message.notEq(this));}
return data;}
get removeParams(){return{attachment_ids:[],attachment_tokens:[],body:"",message_id:this.id,...this.thread.rpcParams,};}
async setDone(){await this.store.env.services.orm.silent.call("mail.message","set_message_done",[[this.id],]);}
async toggleStar(){this.store.insert(await this.store.env.services.orm.silent.call("mail.message","toggle_message_starred",[[this.id]]));}
async unfollow(){if(this.needaction){await this.setDone();}
const thread=this.thread;await thread.selfFollower.remove();this.store.env.services.notification.add(_t('You are no longer following "%(thread_name)s".',{thread_name:thread.name}),{type:"success"});}
get channelMemberHaveSeen(){return this.thread.membersThatCanSeen.filter((m)=>m.hasSeen(this)&&m.persona.notEq(this.author));}
onClickMarkAsUnread(thr){const message=toRaw(this);const thread=toRaw(thr);if(!thread.selfMember||thread.selfMember?.new_message_separator===message.id){return;}
return rpc("/discuss/channel/mark_as_unread",{channel_id:message.thread.id,message_id:message.id,});}}
Message.register();return __exports;});;

/* /mail/static/src/core/common/message_notification_popover.js */
odoo.define('@mail/core/common/message_notification_popover',['@odoo/owl'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const MessageNotificationPopover=__exports.MessageNotificationPopover=class MessageNotificationPopover extends Component{static template="mail.MessageNotificationPopover";static props=["message","close?"];}
return __exports;});;

/* /mail/static/src/core/common/message_reaction_button.js */
odoo.define('@mail/core/common/message_reaction_button',['@odoo/owl','@web/core/emoji_picker/emoji_picker','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{Component,useRef,useState}=require("@odoo/owl");const{useEmojiPicker}=require("@web/core/emoji_picker/emoji_picker");const{useService}=require("@web/core/utils/hooks");const MessageReactionButton=__exports.MessageReactionButton=class MessageReactionButton extends Component{static template="mail.MessageReactionButton";static props=["message","classNames?","action"];setup(){super.setup();this.store=useState(useService("mail.store"));this.emojiPickerRef=useRef("emoji-picker");this.emojiPicker=useEmojiPicker(this.emojiPickerRef,{onSelect:(emoji)=>{const reaction=this.props.message.reactions.find(({content,personas})=>content===emoji&&this.props.message.effectiveSelf.in(personas));if(!reaction){this.props.message.react(emoji);}},});}}
return __exports;});;

/* /mail/static/src/core/common/message_reaction_list.js */
odoo.define('@mail/core/common/message_reaction_list',['@mail/utils/common/hooks','@odoo/owl','@web/core/dropdown/dropdown','@web/core/dropdown/dropdown_hooks','@web/core/emoji_picker/emoji_picker','@web/core/l10n/translation','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{useHover}=require("@mail/utils/common/hooks");const{Component,onMounted,onPatched,useState}=require("@odoo/owl");const{Dropdown}=require("@web/core/dropdown/dropdown");const{useDropdownState}=require("@web/core/dropdown/dropdown_hooks");const{loadEmoji,loader}=require("@web/core/emoji_picker/emoji_picker");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const MessageReactionList=__exports.MessageReactionList=class MessageReactionList extends Component{static template="mail.MessageReactionList";static components={Dropdown};static props=["message","openReactionMenu","reaction"];setup(){super.setup();this.loadEmoji=loadEmoji;this.store=useState(useService("mail.store"));this.ui=useService("ui");this.preview=useDropdownState();this.hover=useHover(["reactionButton","reactionList*"],{onHover:()=>(this.preview.isOpen=true),onAway:()=>(this.preview.isOpen=false),stateObserver:()=>[this.preview?.isOpen],});this.state=useState({emojiLoaded:Boolean(loader.loaded)});if(!loader.loaded){loader.onEmojiLoaded(()=>(this.state.emojiLoaded=true));}
onMounted(()=>void this.state.emojiLoaded);onPatched(()=>void this.state.emojiLoaded);}
previewText(reaction){const{count,content:emoji}=reaction;const personNames=reaction.personas.slice(0,3).map((persona)=>persona.name);const shortcode=loader.loaded?.emojiValueToShortcode?.[emoji]??"?";switch(count){case 1:return _t("%(emoji)s reacted by %(person)s",{emoji:shortcode,person:personNames[0],});case 2:return _t("%(emoji)s reacted by %(person1)s and %(person2)s",{emoji:shortcode,person1:personNames[0],person2:personNames[1],});case 3:return _t("%(emoji)s reacted by %(person1)s, %(person2)s, and %(person3)s",{emoji:shortcode,person1:personNames[0],person2:personNames[1],person3:personNames[2],});case 4:return _t("%(emoji)s reacted by %(person1)s, %(person2)s, %(person3)s, and 1 other",{emoji:shortcode,person1:personNames[0],person2:personNames[1],person3:personNames[2],});default:return _t("%(emoji)s reacted by %(person1)s, %(person2)s, %(person3)s, and %(count)s others",{count:count-3,emoji:shortcode,person1:personNames[0],person2:personNames[1],person3:personNames[2],});}}
hasSelfReacted(reaction){return this.props.message.effectiveSelf.in(reaction.personas);}
onClickReaction(reaction){if(!this.props.message.canAddReaction()){return;}
if(this.hasSelfReacted(reaction)){reaction.remove();}else{this.props.message.react(reaction.content);}}
onContextMenu(ev){if(this.ui.isSmall){ev.preventDefault();this.props.openReactionMenu();}}
onClickReactionList(reaction){this.preview.isOpen=false;this.props.openReactionMenu(reaction);}}
return __exports;});;

/* /mail/static/src/core/common/message_reaction_menu.js */
odoo.define('@mail/core/common/message_reaction_menu',['@web/core/emoji_picker/emoji_picker','@mail/utils/common/hooks','@odoo/owl','@web/core/dialog/dialog','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{loadEmoji,loader}=require("@web/core/emoji_picker/emoji_picker");const{onExternalClick}=require("@mail/utils/common/hooks");const{Component,onMounted,onPatched,useEffect,useExternalListener,useRef,useState,}=require("@odoo/owl");const{Dialog}=require("@web/core/dialog/dialog");const{useService}=require("@web/core/utils/hooks");const MessageReactionMenu=__exports.MessageReactionMenu=class MessageReactionMenu extends Component{static props=["close","message","initialReaction?"];static components={Dialog};static template="mail.MessageReactionMenu";setup(){super.setup();this.root=useRef("root");this.store=useState(useService("mail.store"));this.ui=useState(useService("ui"));this.state=useState({emojiLoaded:Boolean(loader.loaded),reaction:this.props.initialReaction?this.props.initialReaction:this.props.message.reactions[0],});useExternalListener(document,"keydown",this.onKeydown);onExternalClick("root",()=>this.props.close());useEffect(()=>{const activeReaction=this.props.message.reactions.find(({content})=>content===this.state.reaction.content);if(this.props.message.reactions.length===0){this.props.close();}else if(!activeReaction){this.state.reaction=this.props.message.reactions[0];}},()=>[this.props.message.reactions.length]);onMounted(async()=>{if(!loader.loaded){loadEmoji();}});if(!loader.loaded){loader.onEmojiLoaded(()=>(this.state.emojiLoaded=true));}
onMounted(()=>void this.state.emojiLoaded);onPatched(()=>void this.state.emojiLoaded);}
onKeydown(ev){switch(ev.key){case"Escape":this.props.close();break;case"q":this.props.close();break;default:return;}}
getEmojiShortcode(reaction){return loader.loaded?.emojiValueToShortcode?.[reaction.content]??"?";}}
return __exports;});;

/* /mail/static/src/core/common/message_reactions.js */
odoo.define('@mail/core/common/message_reactions',['@odoo/owl','@mail/core/common/message_reaction_list','@web/core/utils/hooks','@web/core/emoji_picker/emoji_picker'],function(require){'use strict';let __exports={};const{Component,useRef,useState}=require("@odoo/owl");const{MessageReactionList}=require("@mail/core/common/message_reaction_list");const{useService}=require("@web/core/utils/hooks");const{useEmojiPicker}=require("@web/core/emoji_picker/emoji_picker");const MessageReactions=__exports.MessageReactions=class MessageReactions extends Component{static props=["message","openReactionMenu"];static template="mail.MessageReactions";static components={MessageReactionList};setup(){super.setup();this.store=useState(useService("mail.store"));this.ui=useService("ui");this.addRef=useRef("add");this.emojiPicker=useEmojiPicker(this.addRef,{onSelect:(emoji)=>{const reaction=this.props.message.reactions.find(({content,personas})=>content===emoji&&this.props.message.effectiveSelf.in(personas));if(!reaction){this.props.message.react(emoji);}},});}}
return __exports;});;

/* /mail/static/src/core/common/message_reactions_model.js */
odoo.define('@mail/core/common/message_reactions_model',['@mail/core/common/record','@web/core/network/rpc'],function(require){'use strict';let __exports={};const{AND,Record}=require("@mail/core/common/record");const{rpc}=require("@web/core/network/rpc");const MessageReactions=__exports.MessageReactions=class MessageReactions extends Record{static id=AND("message","content");static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
content;count;sequence;personas=Record.many("Persona");message=Record.one("Message");async remove(){this.store.insert(await rpc("/mail/message/reaction",{action:"remove",content:this.content,message_id:this.message.id,...this.message.thread.rpcParams,},{silent:true}));}}
MessageReactions.register();return __exports;});;

/* /mail/static/src/core/common/message_search_hook.js */
odoo.define('@mail/core/common/message_search_hook',['@mail/utils/common/hooks','@mail/utils/common/html','@odoo/owl','@web/core/utils/hooks','@web/core/utils/strings'],function(require){'use strict';let __exports={};const{useSequential}=require("@mail/utils/common/hooks");const{createDocumentFragmentFromContent}=require("@mail/utils/common/html");const{useState,onWillUnmount,markup}=require("@odoo/owl");const{useService}=require("@web/core/utils/hooks");const{escapeRegExp}=require("@web/core/utils/strings");const HIGHLIGHT_CLASS=__exports.HIGHLIGHT_CLASS="o-mail-Message-searchHighlight";__exports.searchHighlight=searchHighlight;function searchHighlight(searchTerm,target){if(!searchTerm){return target;}
const htmlDoc=createDocumentFragmentFromContent(target);for(const term of searchTerm.split(" ")){const regexp=new RegExp(`(${escapeRegExp(term)})`,"gi");const split=term.toLowerCase().split("'");let lowercase=split.map((s)=>`'${s}'`).join(', "\'", ');let uppercase=lowercase.toUpperCase();if(split.length>1){lowercase=`concat(${lowercase})`;uppercase=`concat(${uppercase})`;}
const matchs=htmlDoc.evaluate(`//*[text()[contains(translate(., ${uppercase}, ${lowercase}), ${lowercase})]]`,htmlDoc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);for(let i=0;i<matchs.snapshotLength;i++){const element=matchs.snapshotItem(i);const newNode=[];for(const node of element.childNodes){const match=node.textContent.match(regexp);if(node.nodeType===Node.TEXT_NODE&&match?.length>0){let curIndex=0;for(const match of node.textContent.matchAll(regexp)){const start=htmlDoc.createTextNode(node.textContent.slice(curIndex,match.index));newNode.push(start);const span=htmlDoc.createElement("span");span.setAttribute("class",HIGHLIGHT_CLASS);span.textContent=match[0];newNode.push(span);curIndex=match.index+match[0].length;}
const end=htmlDoc.createTextNode(node.textContent.slice(curIndex));newNode.push(end);}else{newNode.push(node);}}
element.replaceChildren(...newNode);}}
return markup(htmlDoc.body.innerHTML);}
__exports.useMessageSearch=useMessageSearch;function useMessageSearch(thread){const store=useService("mail.store");const sequential=useSequential();const state=useState({thread,async search(before=false){if(this.searchTerm){this.searching=true;const data=await sequential(()=>store.search(this.searchTerm,this.thread,before));if(!data){return;}
const{count,loadMore,messages}=data;this.searched=true;this.searching=false;this.count=count;this.loadMore=loadMore;if(before){this.messages.push(...messages);}else{this.messages=messages;}}else{this.clear();}},count:0,clear(){this.messages=[];this.searched=false;this.searching=false;this.searchTerm=undefined;},loadMore:false,messages:[],searchTerm:undefined,searched:false,searching:false,highlight:(target)=>searchHighlight(state.searchTerm,target),});onWillUnmount(()=>{state.clear();});return state;}
return __exports;});;

/* /mail/static/src/core/common/message_seen_indicator.js */
odoo.define('@mail/core/common/message_seen_indicator',['@odoo/owl','@web/core/dialog/dialog','@web/core/l10n/translation','@web/core/utils/hooks','@web/core/browser/browser'],function(require){'use strict';let __exports={};const{Component,useExternalListener,useRef}=require("@odoo/owl");const{Dialog}=require("@web/core/dialog/dialog");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{browser}=require("@web/core/browser/browser");class MessageSeenIndicatorDialog extends Component{static components={Dialog};static template="mail.MessageSeenIndicatorDialog";static props=["message","close?"];setup(){super.setup();this.contentRef=useRef("content");useExternalListener(browser,"click",(ev)=>{if(!this.contentRef?.el.contains(ev.target)){this.props.close();}},true);}}
const MessageSeenIndicator=__exports.MessageSeenIndicator=class MessageSeenIndicator extends Component{static template="mail.MessageSeenIndicator";static props=["message","thread","className?"];setup(){super.setup();this.dialog=useService("dialog");}
get summary(){if(this.props.message.hasEveryoneSeen){if(this.props.thread.correspondent&&this.props.thread.channelMembers.length===2){return _t("Seen by %(user)s",{user:this.props.thread.correspondent.name});}
return _t("Seen by everyone");}
const seenMembers=this.props.message.channelMemberHaveSeen;const[user1,user2,user3]=seenMembers.map((member)=>member.name);switch(seenMembers.length){case 0:return _t("Sent");case 1:return _t("Seen by %(user)s",{user:user1});case 2:return _t("Seen by %(user1)s and %(user2)s",{user1,user2});case 3:return _t("Seen by %(user1)s, %(user2)s and %(user3)s",{user1,user2,user3});case 4:return _t("Seen by %(user1)s, %(user2)s, %(user3)s and 1 other",{user1,user2,user3,});default:return _t("Seen by %(user1)s, %(user2)s, %(user3)s and %(count)s others",{user1,user2,user3,count:seenMembers.length-3,});}}
openDialog(){if(this.props.message.channelMemberHaveSeen.length===0){return;}
this.dialog.add(MessageSeenIndicatorDialog,{message:this.props.message});}}
return __exports;});;

/* /mail/static/src/core/common/navigable_list.js */
odoo.define('@mail/core/common/navigable_list',['@mail/core/common/im_status','@mail/utils/common/hooks','@web/core/utils/misc','@odoo/owl','@web/core/hotkeys/hotkey_service','@web/core/position/position_hook','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{ImStatus}=require("@mail/core/common/im_status");const{onExternalClick}=require("@mail/utils/common/hooks");const{markEventHandled,isEventHandled}=require("@web/core/utils/misc");const{Component,useEffect,useExternalListener,useRef,useState}=require("@odoo/owl");const{getActiveHotkey}=require("@web/core/hotkeys/hotkey_service");const{usePosition}=require("@web/core/position/position_hook");const{useService}=require("@web/core/utils/hooks");const NavigableList=__exports.NavigableList=class NavigableList extends Component{static components={ImStatus};static template="mail.NavigableList";static props={anchorRef:{optional:true},autoSelectFirst:{type:Boolean,optional:true},class:{type:String,optional:true},hint:{type:String,optional:true},onSelect:{type:Function},options:{type:Array},optionTemplate:{type:String,optional:true},position:{type:String,optional:true},isLoading:{type:Boolean,optional:true},};static defaultProps={position:"bottom",isLoading:false,autoSelectFirst:true};setup(){super.setup();this.rootRef=useRef("root");this.state=useState({activeIndex:null,open:false,showLoading:false,});this.hotkey=useService("hotkey");this.hotkeysToRemove=[];useExternalListener(window,"keydown",this.onKeydown,true);onExternalClick("root",async(ev)=>{await new Promise(setTimeout);if(isEventHandled(ev,"composer.onClickTextarea")||isEventHandled(ev,"channelSelector.onClickInput")){return;}
this.close();});usePosition("root",()=>this.props.anchorRef,{position:this.props.position});useEffect(()=>{this.open();},()=>[this.props]);useEffect(()=>{if(!this.props.isLoading){clearTimeout(this.loadingTimeoutId);this.state.showLoading=false;}else if(!this.loadingTimeoutId){this.loadingTimeoutId=setTimeout(()=>(this.state.showLoading=true),2000);}},()=>[this.props.isLoading]);}
get show(){return Boolean(this.state.open&&(this.props.isLoading||this.props.options.length));}
get sortedOptions(){return this.props.options.sort((o1,o2)=>(o1.group??0)-(o2.group??0));}
open(){this.state.open=true;this.state.activeIndex=null;if(this.props.autoSelectFirst){this.navigate("first");}}
close(){this.state.open=false;this.state.activeIndex=null;}
selectOption(ev,index,params={}){const option=this.props.options[index];if(!option){return;}
if(option.unselectable){this.close();return;}
this.props.onSelect(ev,option,{...params,});this.close();}
navigate(direction){if(this.props.options.length===0){return;}
const activeOptionId=this.state.activeIndex!==null?this.state.activeIndex:0;let targetId=undefined;switch(direction){case"first":targetId=0;break;case"last":targetId=this.props.options.length-1;break;case"previous":targetId=activeOptionId-1;if(targetId<0){this.navigate("last");return;}
break;case"next":targetId=activeOptionId+1;if(targetId>this.props.options.length-1){this.navigate("first");return;}
break;default:return;}
this.state.activeIndex=targetId;}
onKeydown(ev){if(!this.show){return;}
const hotkey=getActiveHotkey(ev);switch(hotkey){case"enter":markEventHandled(ev,"NavigableList.select");if(this.state.activeIndex===null){this.close();return;}
this.selectOption(ev,this.state.activeIndex);break;case"escape":markEventHandled(ev,"NavigableList.close");this.close();break;case"tab":this.navigate(this.state.activeIndex===null?"first":"next");break;case"arrowup":this.navigate(this.state.activeIndex===null?"first":"previous");break;case"arrowdown":this.navigate(this.state.activeIndex===null?"first":"next");break;default:return;}
if(this.props.options.length!==0){ev.stopPropagation();}
ev.preventDefault();}
onOptionMouseEnter(index){}}
return __exports;});;

/* /mail/static/src/core/common/notification_model.js */
odoo.define('@mail/core/common/notification_model',['@mail/core/common/record','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const{_t}=require("@web/core/l10n/translation");const Notification=__exports.Notification=class Notification extends Record{static id="id";static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
id;message=Record.one("Message",{onDelete(){this.delete();},});notification_status;notification_type;failure=Record.one("Failure",{inverse:"notifications",compute(){const thread=this.message?.thread;if(!this.message?.isSelfAuthored){return;}
const failure=Object.values(this.store.Failure.records).find((f)=>{return(f.resModel===thread?.model&&f.type===this.notification_type&&(f.resModel!=="discuss.channel"||f.resIds.has(thread?.id)));});return this.isFailure?{id:failure?failure.id:this.store.Failure.nextId.value++,}:false;},eager:true,});failure_type;persona=Record.one("Persona");get isFailure(){return["exception","bounce"].includes(this.notification_status);}
get icon(){if(this.isFailure){return"fa fa-envelope";}
return"fa fa-envelope-o";}
get label(){return"";}
get statusIcon(){switch(this.notification_status){case"process":return"fa fa-hourglass-half";case"pending":return"fa fa-paper-plane-o";case"sent":return"fa fa-check";case"bounce":return"fa fa-exclamation";case"exception":return"fa fa-exclamation";case"ready":return"fa fa-send-o";case"canceled":return"fa fa-trash-o";}
return"";}
get statusTitle(){switch(this.notification_status){case"process":return _t("Processing");case"pending":return _t("Sent");case"sent":return _t("Delivered");case"bounce":return _t("Bounced");case"exception":return _t("Error");case"ready":return _t("Ready");case"canceled":return _t("Cancelled");}
return"";}}
Notification.register();return __exports;});;

/* /mail/static/src/core/common/notification_permission_service.js */
odoo.define('@mail/core/common/notification_permission_service',['@odoo/owl','@web/core/browser/browser','@web/core/browser/feature_detection','@web/core/l10n/translation','@web/core/registry'],function(require){'use strict';let __exports={};const{reactive}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{isAndroidApp,isDisplayStandalone,isIOS,isIosApp,}=require("@web/core/browser/feature_detection");const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");async function getIosPwaPermission(){if(browser.location.protocol!=="https:"){return"denied";}
const registration=await browser.navigator.serviceWorker?.getRegistration();return(await registration?.pushManager.permissionState())??"prompt";}
const notificationPermissionService=__exports.notificationPermissionService={dependencies:["notification"],_normalizePermission(permission){switch(permission){case"default":return"prompt";case undefined:return"denied";default:return permission;}},async start(env,services){const notification=services.notification;let permission;try{if(isIOS()&&isDisplayStandalone()){permission={state:await getIosPwaPermission()};}else if(isIOS()){permission={state:"denied"};}else{permission=await browser.navigator?.permissions?.query({name:"notifications",});}}catch{}
const state=reactive({permission:isIosApp()||isAndroidApp()?"denied":this._normalizePermission(permission?.state??browser.Notification?.permission),requestPermission:async()=>{if(browser.Notification&&state.permission==="prompt"){state.permission=this._normalizePermission(await browser.Notification.requestPermission());if(state.permission==="denied"){notification.add(_t("Odoo will not send notifications on this device."),{type:"warning",title:_t("Notifications blocked"),});}else if(state.permission==="granted"){notification.add(_t("Odoo will send notifications on this device!"),{type:"success",title:_t("Notifications allowed"),});}}},});if(permission&&!isIOS()){permission.addEventListener("change",()=>(state.permission=permission.state));}
return state;},};registry.category("services").add("mail.notification.permission",notificationPermissionService);return __exports;});;

/* /mail/static/src/core/common/out_of_focus_service.js */
odoo.define('@mail/core/common/out_of_focus_service',['@mail/utils/common/format','@web/core/browser/browser','@web/core/l10n/translation','@web/core/registry','@web/core/utils/urls'],function(require){'use strict';let __exports={};const{htmlToTextContentInline}=require("@mail/utils/common/format");const{browser}=require("@web/core/browser/browser");const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{url}=require("@web/core/utils/urls");const PREVIEW_MSG_MAX_SIZE=350;const OutOfFocusService=__exports.OutOfFocusService=class OutOfFocusService{constructor(env,services){this.setup(env,services);}
setup(env,services){this.env=env;this.audio=undefined;this.multiTab=services.multi_tab;this.notificationService=services.notification;this.closeFuncs=[];}
async notify(message,thread){const modelsHandleByPush=["mail.thread","discuss.channel"];if(modelsHandleByPush.includes(message.thread?.model)&&(await this.hasServiceWorkInstalledAndPushSubscriptionActive())){return;}
const author=message.author;let notificationTitle;let icon="/mail/static/src/img/odoobot_transparent.png";if(!author){notificationTitle=_t("New message");}else{icon=author.avatarUrl;if(message.thread?.channel_type==="channel"){notificationTitle=_t("%(author name)s from %(channel name)s",{"author name":author.name,"channel name":message.thread.displayName,});}else{notificationTitle=author.name;}}
const notificationContent=htmlToTextContentInline(message.body).substring(0,PREVIEW_MSG_MAX_SIZE);this.sendNotification({message:notificationContent,sound:message.thread?.model==="discuss.channel",title:notificationTitle,type:"info",icon,});}
async hasServiceWorkInstalledAndPushSubscriptionActive(){const registration=await browser.navigator.serviceWorker?.getRegistration();if(registration){const pushManager=await registration.pushManager;if(pushManager){const subscription=await pushManager.getSubscription();return!!subscription;}}
return false;}
sendNotification({message,sound=true,title,type,icon}){if(!this.canSendNativeNotification){this.sendOdooNotification(message,{sound,title,type});return;}
if(!this.multiTab.isOnMainTab()){return;}
try{this.sendNativeNotification(title,message,icon,{sound});}catch(error){if(error.message.includes("ServiceWorkerRegistration")){this.sendOdooNotification(message,{sound,title,type});}else{throw error;}}}
async sendOdooNotification(message,options){const{sound}=options;delete options.sound;this.closeFuncs.push(this.notificationService.add(message,options));if(this.closeFuncs.length>3){this.closeFuncs.shift()();}
if(sound){this._playSound();}}
sendNativeNotification(title,message,icon,{sound=true}={}){const notification=new Notification(title,{body:message,icon,});notification.addEventListener("click",({target:notification})=>{window.focus();notification.close();});if(sound){this._playSound();}}
async _playSound(){if(this.canPlayAudio&&this.multiTab.isOnMainTab()){if(!this.audio){this.audio=new Audio();this.audio.src=this.audio.canPlayType("audio/ogg; codecs=vorbis")?url("/mail/static/src/audio/ting.ogg"):url("/mail/static/src/audio/ting.mp3");}
try{await this.audio.play();}catch{}}}
get canPlayAudio(){return typeof Audio!=="undefined";}
get canSendNativeNotification(){return Boolean(browser.Notification&&browser.Notification.permission==="granted");}}
const outOfFocusService=__exports.outOfFocusService={dependencies:["multi_tab","notification"],start(env,services){const service=new OutOfFocusService(env,services);return service;},};registry.category("services").add("mail.out_of_focus",outOfFocusService);return __exports;});;

/* /mail/static/src/core/common/partner_compare.js */
odoo.define('@mail/core/common/partner_compare',['@mail/utils/common/format','@web/core/registry'],function(require){'use strict';let __exports={};const{cleanTerm}=require("@mail/utils/common/format");const{registry}=require("@web/core/registry");const partnerCompareRegistry=__exports.partnerCompareRegistry=registry.category("mail.partner_compare");partnerCompareRegistry.add("mail.archived-last-except-odoobot",(p1,p2)=>{const p1active=p1.active||p1.eq(p1.store.odoobot);const p2active=p2.active||p2.eq(p2.store.odoobot);if(!p1active&&p2active){return 1;}
if(!p2active&&p1active){return-1;}},{sequence:5});partnerCompareRegistry.add("mail.internal-users",(p1,p2)=>{const isAInternalUser=p1.isInternalUser;const isBInternalUser=p2.isInternalUser;if(isAInternalUser&&!isBInternalUser){return-1;}
if(!isAInternalUser&&isBInternalUser){return 1;}},{sequence:35});partnerCompareRegistry.add("mail.followers",(p1,p2,{thread})=>{if(thread){const followerList=[...thread.followers];if(thread.selfFollower){followerList.push(thread.selfFollower);}
const isFollower1=followerList.some((follower)=>p1.eq(follower.partner));const isFollower2=followerList.some((follower)=>p2.eq(follower.partner));if(isFollower1&&!isFollower2){return-1;}
if(!isFollower1&&isFollower2){return 1;}}},{sequence:25});partnerCompareRegistry.add("mail.name",(p1,p2,{searchTerm})=>{const cleanedName1=cleanTerm(p1.name);const cleanedName2=cleanTerm(p2.name);if(cleanedName1.startsWith(searchTerm)&&!cleanedName2.startsWith(searchTerm)){return-1;}
if(!cleanedName1.startsWith(searchTerm)&&cleanedName2.startsWith(searchTerm)){return 1;}
if(cleanedName1<cleanedName2){return-1;}
if(cleanedName1>cleanedName2){return 1;}},{sequence:50});partnerCompareRegistry.add("mail.email",(p1,p2,{searchTerm})=>{const cleanedEmail1=cleanTerm(p1.email);const cleanedEmail2=cleanTerm(p2.email);if(cleanedEmail1.startsWith(searchTerm)&&!cleanedEmail2.startsWith(searchTerm)){return-1;}
if(!cleanedEmail1.startsWith(searchTerm)&&cleanedEmail2.startsWith(searchTerm)){return 1;}
if(cleanedEmail1<cleanedEmail2){return-1;}
if(cleanedEmail1>cleanedEmail2){return 1;}},{sequence:55});partnerCompareRegistry.add("mail.id",(p1,p2)=>{return p1.id-p2.id;},{sequence:75});return __exports;});;

/* /mail/static/src/core/common/persona_model.js */
odoo.define('@mail/core/common/persona_model',['@mail/core/common/record','@web/core/utils/urls','@web/core/network/rpc','@web/core/utils/timing'],function(require){'use strict';let __exports={};const{AND,Record}=require("@mail/core/common/record");const{imageUrl}=require("@web/core/utils/urls");const{rpc}=require("@web/core/network/rpc");const{debounce}=require("@web/core/utils/timing");const Persona=__exports.Persona=class Persona extends Record{static id=AND("type","id");static records={};static get(data){return super.get(data);}
static insert(data){return super.insert(...arguments);}
static new(){const record=super.new(...arguments);record.debouncedSetImStatus=debounce((newStatus)=>record.updateImStatus(newStatus),this.IM_STATUS_DEBOUNCE_DELAY);return record;}
static IM_STATUS_DEBOUNCE_DELAY=1000;avatar_128_access_token;channelMembers=Record.many("ChannelMember");id;is_company;landlineNumber;mobileNumber;debouncedSetImStatus;storeAsTrackedImStatus=Record.one("Store",{compute(){if(this.type==="guest"||(this.type==="partner"&&this.im_status!=="im_partner"&&!this.is_public)){return this.store;}},onAdd(){if(!this.store.env.services.bus_service.isActive){return;}
const model=this.type==="partner"?"res.partner":"mail.guest";this.store.env.services.bus_service.addChannel(`odoo-presence-${model}_${this.id}`);},onDelete(){if(!this.store.env.services.bus_service.isActive){return;}
const model=this.type==="partner"?"res.partner":"mail.guest";this.store.env.services.bus_service.deleteChannel(`odoo-presence-${model}_${this.id}`);},eager:true,inverse:"imStatusTrackedPersonas",});signature=Record.attr("",{html:true});type;name;country=Record.one("Country");email;userId;im_status=Record.attr(null,{onUpdate(){if(this.eq(this.store.self)&&this.im_status==="offline"){this.store.env.services.im_status.updateBusPresence();}},});notification_preference;isAdmin=false;isInternalUser=false;write_date=Record.attr(undefined,{type:"datetime"});groups_id=Record.many("res.groups",{inverse:"personas"});get hasPhoneNumber(){return Boolean(this.mobileNumber||this.landlineNumber);}
get emailWithoutDomain(){return this.email.substring(0,this.email.lastIndexOf("@"));}
get avatarUrl(){const accessTokenParam={};if(!this.store.self.isInternalUser){accessTokenParam.access_token=this.avatar_128_access_token;}
if(this.type==="partner"){return imageUrl("res.partner",this.id,"avatar_128",{...accessTokenParam,unique:this.write_date,});}
if(this.type==="guest"){return imageUrl("mail.guest",this.id,"avatar_128",{...accessTokenParam,unique:this.write_date,});}
if(this.userId){return imageUrl("res.users",this.userId,"avatar_128",{unique:this.write_date,});}
return this.store.DEFAULT_AVATAR;}
searchChat(){return Object.values(this.store.Thread.records).find((thread)=>thread.channel_type==="chat"&&thread.correspondent?.persona.eq(this));}
async updateGuestName(name){await rpc("/mail/guest/update_name",{guest_id:this.id,name,});}
updateImStatus(newStatus){this.im_status=newStatus;}}
Persona.register();return __exports;});;

/* /mail/static/src/core/common/picker.js */
odoo.define('@mail/core/common/picker',['@odoo/owl','@web/core/browser/browser','@web/core/utils/misc','@web/core/popover/popover_hook','@web/core/utils/hooks','@mail/core/common/picker_content','@mail/utils/common/hooks'],function(require){'use strict';let __exports={};const{Component,useExternalListener,useState}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{isEventHandled}=require("@web/core/utils/misc");const{usePopover}=require("@web/core/popover/popover_hook");const{useService}=require("@web/core/utils/hooks");const{PickerContent}=require("@mail/core/common/picker_content");const{useLazyExternalListener}=require("@mail/utils/common/hooks");__exports.usePicker=usePicker;function usePicker(setting){const storeScroll={scrollValue:0,set:(value)=>(storeScroll.scrollValue=value),get:()=>storeScroll.scrollValue,};const PICKERS={NONE:"none",EMOJI:"emoji",GIF:"gif",};return useState({PICKERS,anchor:setting.anchor,buttons:setting.buttons,close:setting.close,pickers:setting.pickers,position:setting.position,state:{picker:PICKERS.NONE,searchTerm:"",},storeScroll,});}
const Picker=__exports.Picker=class Picker extends Component{static components={PickerContent,};static props=["PICKERS","anchor?","buttons","close?","state","pickers","position?","storeScroll","fixed?",];static template="mail.Picker";setup(){this.ui=useState(useService("ui"));this.popover=usePopover(PickerContent,this.popoverSettings);useExternalListener(browser,"click",async(ev)=>{if(this.props.state.picker===this.props.PICKERS.NONE){return;}
await new Promise(setTimeout);if(!this.isEventHandledByPicker(ev)){this.close();}},true);for(const button of this.props.buttons){useLazyExternalListener(()=>button.el,"click",async(ev)=>this.toggle(this.props.anchor?.el??button.el,ev));}}
get popoverSettings(){return{popoverClass:"border-secondary",position:this.props.position,fixedPosition:this.props.fixed,onClose:()=>this.close(),closeOnClickAway:false,animation:false,arrow:false,};}
get contentProps(){const pickers={};for(const[name,fn]of Object.entries(this.props.pickers)){pickers[name]=(str,resetOnSelect)=>{fn(str);if(resetOnSelect){this.close();}};}
return{PICKERS:this.props.PICKERS,close:()=>this.close(),pickers,state:this.props.state,storeScroll:this.props.storeScroll,};}
isEventHandledByPicker(ev){return(isEventHandled(ev,"Composer.onClickAddEmoji")||isEventHandled(ev,"PickerContent.onClick"));}
async toggle(el,ev){await new Promise(setTimeout);if(this.ui.isSmall){if(this.props.state.picker===this.props.PICKERS.NONE){this.props.state.picker=this.props.PICKERS.EMOJI;}else{this.props.state.picker=this.props.PICKERS.NONE;}
return;}
if(isEventHandled(ev,"Composer.onClickAddEmoji")){if(this.popover.isOpen){if(this.props.state.picker===this.props.PICKERS.EMOJI){this.props.state.picker=this.props.PICKERS.NONE;this.popover.close();return;}
this.props.state.picker=this.props.PICKERS.EMOJI;}else{this.props.state.picker=this.props.PICKERS.EMOJI;this.popover.open(el,this.contentProps);}}}
close(){this.props.close?.();this.popover.close();this.props.state.picker=this.props.PICKERS.NONE;this.props.state.searchTerm="";}}
return __exports;});;

/* /mail/static/src/core/common/picker_content.js */
odoo.define('@mail/core/common/picker_content',['@odoo/owl','@web/core/utils/misc','@web/core/emoji_picker/emoji_picker'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{markEventHandled}=require("@web/core/utils/misc");const{EmojiPicker}=require("@web/core/emoji_picker/emoji_picker");const PickerContent=__exports.PickerContent=class PickerContent extends Component{static components={EmojiPicker};static props=["PICKERS","close","pickers","state","storeScroll"];static template="mail.PickerContent";onClick(ev){markEventHandled(ev,"PickerContent.onClick");}}
return __exports;});;

/* /mail/static/src/core/common/record.js */
odoo.define('@mail/core/common/record',['@mail/model/export'],function(require){'use strict';let __exports={};Object.assign(__exports,require("@mail/model/export"));return __exports;});;

/* /mail/static/src/core/common/relative_time.js */
odoo.define('@mail/core/common/relative_time',['@odoo/owl','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Component,onWillDestroy,onWillUpdateProps,xml}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const MINUTE=60*1000;const HOUR=60*MINUTE;const RelativeTime=__exports.RelativeTime=class RelativeTime extends Component{static props=["datetime"];static template=xml`<t t-esc="relativeTime"/>`;setup(){super.setup();this.timeout=null;this.computeRelativeTime(this.props.datetime);onWillDestroy(()=>clearTimeout(this.timeout));onWillUpdateProps((nextProps)=>{clearTimeout(this.timeout);this.computeRelativeTime(nextProps.datetime);});}
computeRelativeTime(datetime){if(!datetime){this.relativeTime="";return;}
const delta=Date.now()-datetime.ts;const absDelta=Math.abs(delta);if(absDelta<45*1000){this.relativeTime=delta<0?_t("in a few seconds"):_t("now");}else{this.relativeTime=datetime.toRelative();}
const updateDelay=absDelta<MINUTE?absDelta:absDelta<HOUR?MINUTE:HOUR;this.timeout=setTimeout(()=>{this.computeRelativeTime(this.props.datetime);this.render();},updateDelay);}}
return __exports;});;

/* /mail/static/src/core/common/res_groups_model.js */
odoo.define('@mail/core/common/res_groups_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const ResGroups=__exports.ResGroups=class ResGroups extends Record{static _name="res.groups";static id="id";personas=Record.many("Persona");}
ResGroups.register();return __exports;});;

/* /mail/static/src/core/common/search_message_input.js */
odoo.define('@mail/core/common/search_message_input',['@odoo/owl','@web/core/browser/browser','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{Component,useExternalListener,useState}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{useAutofocus}=require("@web/core/utils/hooks");const SearchMessageInput=__exports.SearchMessageInput=class SearchMessageInput extends Component{static template="mail.SearchMessageInput";static props=["closeSearch?","messageSearch","thread"];setup(){super.setup();this.state=useState({searchTerm:"",searchedTerm:""});useAutofocus();useExternalListener(browser,"keydown",(ev)=>{if(ev.key==="Escape"){this.props.closeSearch?.();}},{capture:true});}
search(){this.props.messageSearch.searchTerm=this.state.searchTerm;this.props.messageSearch.search();this.state.searchedTerm=this.state.searchTerm;}
clear(){this.state.searchTerm="";this.state.searchedTerm=this.state.searchTerm;this.props.messageSearch.clear();this.props.closeSearch?.();}
onKeydownSearch(ev){if(ev.key!=="Enter"){return;}
if(!this.state.searchTerm){this.clear();}else{this.search();}}}
return __exports;});;

/* /mail/static/src/core/common/search_message_result.js */
odoo.define('@mail/core/common/search_message_result',['@odoo/owl','@mail/core/common/message_card_list','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{MessageCardList}=require("@mail/core/common/message_card_list");const{_t}=require("@web/core/l10n/translation");const SearchMessageResult=__exports.SearchMessageResult=class SearchMessageResult extends Component{static template="mail.SearchMessageResult";static components={MessageCardList};static props=["thread","messageSearch","onClickJump?"];get MESSAGE_FOUND(){if(this.props.messageSearch.messages.length===0){return false;}
return _t("%s messages found",this.props.messageSearch.count);}
onLoadMoreVisible(){const before=this.props.messageSearch?.messages?Math.min(...this.props.messageSearch.messages.map((message)=>message.id)):false;this.props.messageSearch.search(before);}}
return __exports;});;

/* /mail/static/src/core/common/search_messages_panel.js */
odoo.define('@mail/core/common/search_messages_panel',['@odoo/owl','@mail/discuss/core/common/action_panel','@web/core/l10n/translation','@web/core/utils/hooks','@mail/core/common/search_message_input','@mail/core/common/search_message_result','@mail/core/common/message_search_hook'],function(require){'use strict';let __exports={};const{Component,useState,onWillUpdateProps}=require("@odoo/owl");const{ActionPanel}=require("@mail/discuss/core/common/action_panel");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{SearchMessageInput}=require("@mail/core/common/search_message_input");const{SearchMessageResult}=require("@mail/core/common/search_message_result");const{useMessageSearch}=require("@mail/core/common/message_search_hook");const SearchMessagesPanel=__exports.SearchMessagesPanel=class SearchMessagesPanel extends Component{static template="mail.SearchMessagesPanel";static components={ActionPanel,SearchMessageInput,SearchMessageResult};static props=["thread"];setup(){super.setup();this.store=useState(useService("mail.store"));this.messageSearch=useMessageSearch(this.props.thread);onWillUpdateProps((nextProps)=>{if(this.props.thread.notEq(nextProps.thread)){this.env.searchMenu?.close();}});}
get title(){return _t("Search Message");}}
return __exports;});;

/* /mail/static/src/core/common/settings_model.js */
odoo.define('@mail/core/common/settings_model',['@web/core/l10n/translation','@web/core/utils/strings','@web/core/browser/browser','@mail/core/common/record','@web/core/utils/timing','@web/core/network/rpc'],function(require){'use strict';let __exports={};const{_t}=require("@web/core/l10n/translation");const{sprintf}=require("@web/core/utils/strings");const{browser}=require("@web/core/browser/browser");const{Record}=require("@mail/core/common/record");const{debounce}=require("@web/core/utils/timing");const{rpc}=require("@web/core/network/rpc");const Settings=__exports.Settings=class Settings extends Record{id;setup(){super.setup();this.saveVoiceThresholdDebounce=debounce(()=>{browser.localStorage.setItem("mail_user_setting_voice_threshold",this.voiceActivationThreshold.toString());},2000);this.hasCanvasFilterSupport=typeof document.createElement("canvas").getContext("2d").filter!=="undefined";this._loadLocalSettings();}
channel_notifications=Record.attr("mentions",{compute(){return this.channel_notifications===false?"mentions":this.channel_notifications;},});mute_until_dt=Record.attr(false,{type:"datetime"});audioInputDeviceId="";use_push_to_talk=false;voice_active_duration=200;volumes=Record.many("Volume");volumeSettingsTimeouts=new Map();voiceActivationThreshold=0.05;isRegisteringKey=false;push_to_talk_key;backgroundBlurAmount=10;edgeBlurAmount=10;showOnlyVideo=false;useBlur=false;logRtc=false;get audioConstraints(){const constraints={echoCancellation:true,noiseSuppression:true,};if(this.audioInputDeviceId){constraints.deviceId=this.audioInputDeviceId;}
return constraints;}
get NOTIFICATIONS(){return[{label:"all",name:_t("All Messages"),},{label:"mentions",name:_t("Mentions Only"),},{label:"no_notif",name:_t("Nothing"),},];}
get MUTES(){return[{label:"15_mins",value:15,name:_t("For 15 minutes"),},{label:"1_hour",value:60,name:_t("For 1 hour"),},{label:"3_hours",value:180,name:_t("For 3 hours"),},{label:"8_hours",value:480,name:_t("For 8 hours"),},{label:"24_hours",value:1440,name:_t("For 24 hours"),},{label:"forever",value:-1,name:_t("Until I turn it back on"),},];}
getMuteUntilText(dt){if(dt){return dt.year<=luxon.DateTime.now().year+2?sprintf(_t(`Until %s`),dt.toLocaleString(luxon.DateTime.DATETIME_MED)):_t("Until I turn it back on");}
return undefined;}
async setCustomNotifications(custom_notifications,thread=undefined){return rpc("/discuss/settings/custom_notifications",{custom_notifications:!thread&&custom_notifications==="mentions"?false:custom_notifications,channel_id:thread?.id,});}
async setMuteDuration(minutes,thread=undefined){return rpc("/discuss/settings/mute",{minutes,channel_id:thread?.id,});}
async setAudioInputDevice(audioInputDeviceId){this.audioInputDeviceId=audioInputDeviceId;browser.localStorage.setItem("mail_user_setting_audio_input_device_id",audioInputDeviceId);}
setDelayValue(value){this.voice_active_duration=parseInt(value,10);this._saveSettings();}
async setPushToTalkKey(ev){const nonElligibleKeys=new Set(["Shift","Control","Alt","Meta"]);let pushToTalkKey=`${ev.shiftKey || ""}.${ev.ctrlKey || ev.metaKey || ""}.${
            ev.altKey || ""
        }`;if(!nonElligibleKeys.has(ev.key)){pushToTalkKey+=`.${ev.key === " " ? "Space" : ev.key}`;}
this.push_to_talk_key=pushToTalkKey;this._saveSettings();}
async saveVolumeSetting({partnerId,guestId,volume}){if(this.store.self.type!=="partner"){return;}
const key=`${partnerId}_${guestId}`;if(this.volumeSettingsTimeouts.get(key)){browser.clearTimeout(this.volumeSettingsTimeouts.get(key));}
this.volumeSettingsTimeouts.set(key,browser.setTimeout(this._onSaveVolumeSettingTimeout.bind(this,{key,partnerId,guestId,volume}),5000));}
setThresholdValue(voiceActivationThreshold){this.voiceActivationThreshold=voiceActivationThreshold;this.saveVoiceThresholdDebounce();}
buildKeySet({shiftKey,ctrlKey,altKey,key}){const keys=new Set();if(key){keys.add(key==="Meta"?"Alt":key);}
if(shiftKey){keys.add("Shift");}
if(ctrlKey){keys.add("Control");}
if(altKey){keys.add("Alt");}
return keys;}
isPushToTalkKey(ev){if(!this.use_push_to_talk||!this.push_to_talk_key){return false;}
const[shiftKey,ctrlKey,altKey,key]=this.push_to_talk_key.split(".");const settingsKeySet=this.buildKeySet({shiftKey,ctrlKey,altKey,key});const eventKeySet=this.buildKeySet({shiftKey:ev.shiftKey,ctrlKey:ev.ctrlKey,altKey:ev.altKey,key:ev.key,});if(ev.type==="keydown"){return[...settingsKeySet].every((key)=>eventKeySet.has(key));}
return settingsKeySet.has(ev.key==="Meta"?"Alt":ev.key);}
pushToTalkKeyFormat(){if(!this.push_to_talk_key){return;}
const[shiftKey,ctrlKey,altKey,key]=this.push_to_talk_key.split(".");return{shiftKey:!!shiftKey,ctrlKey:!!ctrlKey,altKey:!!altKey,key:key||false,};}
setPushToTalk(value){this.use_push_to_talk=value;this._saveSettings();}
_loadLocalSettings(){const voiceActivationThresholdString=browser.localStorage.getItem("mail_user_setting_voice_threshold");this.voiceActivationThreshold=voiceActivationThresholdString?parseFloat(voiceActivationThresholdString):this.voiceActivationThreshold;this.audioInputDeviceId=browser.localStorage.getItem("mail_user_setting_audio_input_device_id");this.showOnlyVideo=browser.localStorage.getItem("mail_user_setting_show_only_video")==="true";this.useBlur=browser.localStorage.getItem("mail_user_setting_use_blur")==="true";const backgroundBlurAmount=browser.localStorage.getItem("mail_user_setting_background_blur_amount");this.backgroundBlurAmount=backgroundBlurAmount?parseInt(backgroundBlurAmount):10;const edgeBlurAmount=browser.localStorage.getItem("mail_user_setting_edge_blur_amount");this.edgeBlurAmount=edgeBlurAmount?parseInt(edgeBlurAmount):10;}
async _onSaveGlobalSettingsTimeout(){this.globalSettingsTimeout=undefined;await this.store.env.services.orm.call("res.users.settings","set_res_users_settings",[[this.id]],{new_settings:{push_to_talk_key:this.push_to_talk_key,use_push_to_talk:this.use_push_to_talk,voice_active_duration:this.voice_active_duration,},});}
async _onSaveVolumeSettingTimeout({key,partnerId,guestId,volume}){this.volumeSettingsTimeouts.delete(key);await this.store.env.services.orm.call("res.users.settings","set_volume_setting",[[this.id],partnerId,volume],{guest_id:guestId});}
async _saveSettings(){if(this.store.self.type!=="partner"){return;}
browser.clearTimeout(this.globalSettingsTimeout);this.globalSettingsTimeout=browser.setTimeout(()=>this._onSaveGlobalSettingsTimeout(),2000);}}
Settings.register();return __exports;});;

/* /mail/static/src/core/common/sound_effects_service.js */
odoo.define('@mail/core/common/sound_effects_service',['@web/core/browser/browser','@web/core/registry','@web/core/utils/urls'],function(require){'use strict';let __exports={};const{browser}=require("@web/core/browser/browser");const{registry}=require("@web/core/registry");const{url}=require("@web/core/utils/urls");const SoundEffects=__exports.SoundEffects=class SoundEffects{constructor(env){this.soundEffects={"channel-join":{defaultVolume:0.3,path:"/mail/static/src/audio/channel_01_in"},"channel-leave":{path:"/mail/static/src/audio/channel_04_out"},deafen:{defaultVolume:0.15,path:"/mail/static/src/audio/deafen_new_01"},"incoming-call":{defaultVolume:0.15,path:"/mail/static/src/audio/call_02_in_"},"member-leave":{defaultVolume:0.5,path:"/mail/static/src/audio/channel_01_out"},mute:{defaultVolume:0.2,path:"/mail/static/src/audio/mute_1"},"new-message":{path:"/mail/static/src/audio/dm_02"},"push-to-talk-on":{defaultVolume:0.02,path:"/mail/static/src/audio/ptt_push_1"},"push-to-talk-off":{defaultVolume:0.02,path:"/mail/static/src/audio/ptt_release_1",},"screen-sharing":{defaultVolume:0.5,path:"/mail/static/src/audio/share_02"},undeafen:{defaultVolume:0.15,path:"/mail/static/src/audio/undeafen_new_01"},unmute:{defaultVolume:0.2,path:"/mail/static/src/audio/unmute_1"},};}
play(soundEffectName,{loop=false,volume}={}){if(typeof browser.Audio==="undefined"){return;}
const soundEffect=this.soundEffects[soundEffectName];if(!soundEffect){return;}
if(!soundEffect.audio){const audio=new browser.Audio();const ext=audio.canPlayType("audio/ogg; codecs=vorbis")?".ogg":".mp3";audio.src=url(soundEffect.path+ext);soundEffect.audio=audio;}
if(!soundEffect.audio.paused){soundEffect.audio.pause();}
soundEffect.audio.currentTime=0;soundEffect.audio.loop=loop;soundEffect.audio.volume=volume??soundEffect.defaultVolume??1;Promise.resolve(soundEffect.audio.play()).catch(()=>{});}
stop(soundEffectName){const soundEffect=this.soundEffects[soundEffectName];if(soundEffect){if(soundEffect.audio){soundEffect.audio.pause();soundEffect.audio.currentTime=0;}}else{for(const soundEffect of Object.values(this.soundEffects)){if(soundEffect.audio){soundEffect.audio.pause();soundEffect.audio.currentTime=0;}}}}}
const soundEffects=__exports.soundEffects={start(env){return new SoundEffects(env);},};registry.category("services").add("mail.sound_effects",soundEffects);return __exports;});;

/* /mail/static/src/core/common/store_service.js */
odoo.define('@mail/core/common/store_service',['@mail/utils/common/misc','@web/core/network/rpc','@mail/core/common/record','@odoo/owl','@web/core/registry','@web/core/user','@web/core/utils/concurrency','@web/core/utils/timing','@web/session','@web/core/l10n/translation','@mail/utils/common/format','@web/core/browser/browser'],function(require){'use strict';let __exports={};const{compareDatetime}=require("@mail/utils/common/misc");const{rpc}=require("@web/core/network/rpc");const{Store:BaseStore,makeStore,Record}=require("@mail/core/common/record");const{reactive}=require("@odoo/owl");const{registry}=require("@web/core/registry");const{user}=require("@web/core/user");const{Deferred,Mutex}=require("@web/core/utils/concurrency");const{debounce}=require("@web/core/utils/timing");const{session}=require("@web/session");const{_t}=require("@web/core/l10n/translation");const{cleanTerm,prettifyMessageContent}=require("@mail/utils/common/format");const{browser}=require("@web/core/browser/browser");let prevLastMessageId=null;let temporaryIdOffset=0.01;const pyToJsModels=__exports.pyToJsModels={"discuss.channel.member":"ChannelMember","discuss.channel.rtc.session":"RtcSession","discuss.channel":"Thread","ir.attachment":"Attachment","mail.activity":"Activity","mail.guest":"Persona","mail.followers":"Follower","mail.link.preview":"LinkPreview","mail.message":"Message","mail.notification":"Notification","mail.scheduled.message":"ScheduledMessage","mail.thread":"Thread","res.partner":"Persona",};const addFieldsByPyModel=__exports.addFieldsByPyModel={"discuss.channel":{model:"discuss.channel"},"mail.guest":{type:"guest"},"res.partner":{type:"partner"},};const Store=__exports.Store=class Store extends BaseStore{static FETCH_DATA_DEBOUNCE_DELAY=1;static OTHER_LONG_TYPING=60000;FETCH_LIMIT=30;DEFAULT_AVATAR="/mail/static/src/img/smiley/avatar.jpg";isReady=new Deferred();static insert(){return super.insert(...arguments);}
Activity;Attachment;["mail.canned.response"];ChannelMember;ChatWindow;Composer;Failure;Follower;LinkPreview;Message;MessageReactions;Notification;Persona;["res.groups"];ScheduledMessage;Settings;Thread;Volume;channel_types_with_seen_infos=[];self=Record.one("Persona");inPublicPage=false;odoobot=Record.one("Persona");users={};internalUserGroupId;mt_comment_id;hasMessageTranslationFeature;imStatusTrackedPersonas=Record.many("Persona",{inverse:"storeAsTrackedImStatus",});hasLinkPreviewFeature=true;menu={counter:0};chatHub=Record.one("ChatHub",{compute:()=>({})});failures=Record.many("Failure",{sort:(f1,f2)=>f2.lastMessage?.id-f1.lastMessage?.id,});settings=Record.one("Settings");openInviteThread=Record.one("Thread");fetchDeferred=new Deferred();fetchParams={};fetchReadonly=true;fetchSilent=true;cannedReponses=this.makeCachedFetchData({canned_responses:true});specialMentions=[{isSpecial:true,label:"everyone",channel_types:["channel","group"],displayName:"Everyone",description:_t("Notify everyone"),},];get initMessagingParams(){return{init_messaging:{},};}
isNotificationPermissionDismissed=Record.attr(false,{compute(){return(browser.localStorage.getItem("mail.user_setting.push_notification_dismissed")==="true");},onUpdate(){if(this.isNotificationPermissionDismissed){browser.localStorage.setItem("mail.user_setting.push_notification_dismissed","true");}else{browser.localStorage.removeItem("mail.user_setting.push_notification_dismissed");}},});messagePostMutex=new Mutex();menuThreads=Record.many("Thread",{compute(){const searchTerm=cleanTerm(this.discuss.searchTerm);let threads=Object.values(this.Thread.records).filter((thread)=>(thread.displayToSelf||(thread.needactionMessages.length>0&&thread.model!=="mail.box"))&&cleanTerm(thread.displayName).includes(searchTerm));const tab=this.discuss.activeTab;if(tab!=="main"){threads=threads.filter(({channel_type})=>this.tabToThreadType(tab).includes(channel_type));}else if(tab==="main"&&this.env.inDiscussApp){threads=threads.filter(({channel_type})=>this.tabToThreadType("mailbox").includes(channel_type));}
return threads;},sort(a,b){const aNeedaction=a.needactionMessages.length;const bNeedaction=b.needactionMessages.length;if(aNeedaction>0&&bNeedaction===0){return-1;}
if(bNeedaction>0&&aNeedaction===0){return 1;}
const aUnread=a.selfMember?.message_unread_counter;const bUnread=b.selfMember?.message_unread_counter;if(aUnread>0&&bUnread===0){return-1;}
if(bUnread>0&&aUnread===0){return 1;}
const aMessageDatetime=a.newestPersistentNotEmptyOfAllMessage?.datetime;const bMessageDateTime=b.newestPersistentNotEmptyOfAllMessage?.datetime;if(!aMessageDatetime&&bMessageDateTime){return 1;}
if(!bMessageDateTime&&aMessageDatetime){return-1;}
if(aMessageDatetime&&bMessageDateTime&&aMessageDatetime!==bMessageDateTime){return bMessageDateTime-aMessageDatetime;}
return b.localId>a.localId?1:-1;},});async doMessagePost(params,tmpMessage){return this.messagePostMutex.exec(async()=>{let res;try{res=await rpc("/mail/message/post",params,{silent:true});}catch(err){if(!tmpMessage){throw err;}
tmpMessage.postFailRedo=()=>{tmpMessage.postFailRedo=undefined;tmpMessage.thread.messages.delete(tmpMessage);tmpMessage.thread.messages.add(tmpMessage);this.doMessagePost(params,tmpMessage);};}
return res;});}
async fetchData(params,{readonly=true,silent=true}={}){Object.assign(this.fetchParams,params);this.fetchReadonly=this.fetchReadonly&&readonly;this.fetchSilent=this.fetchSilent&&silent;const fetchDeferred=this.fetchDeferred;this._fetchDataDebounced();return fetchDeferred;}
async initialize(){await this.fetchData(this.initMessagingParams);this.isReady.resolve();}
makeCachedFetchData(params){let def=null;const r=reactive({status:"not_fetched",fetch:()=>{if(["fetching","fetched"].includes(r.status)){return def;}
r.status="fetching";def=new Deferred();this.fetchData(params).then((result)=>{r.status="fetched";def.resolve(result);},(error)=>{r.status="not_fetched";def.reject(error);});return def;},});return r;}
async _fetchDataDebounced(){const fetchDeferred=this.fetchDeferred;this.fetchParams.context={...user.context,...this.fetchParams.context,};rpc(this.fetchReadonly?"/mail/data":"/mail/action",this.fetchParams,{silent:this.fetchSilent,}).then((data)=>{const recordsByModel=this.insert(data,{html:true});fetchDeferred.resolve(recordsByModel);},(error)=>fetchDeferred.reject(error));this.fetchDeferred=new Deferred();this.fetchParams={};this.fetchReadonly=true;this.fetchSilent=true;}
insert(dataByModelName={},options={}){const store=this;const pyModels=Object.values(pyToJsModels);return Record.MAKE_UPDATE(function storeInsert(){const res={};const recordsDataToDelete=[];for(const[pyOrJsModelName,data]of Object.entries(dataByModelName)){if(pyModels.includes(pyOrJsModelName)){console.warn(`store.insert() should receive the python model name instead of “${pyOrJsModelName}”.`);}
const modelName=pyToJsModels[pyOrJsModelName]||pyOrJsModelName;if(!store[modelName]){console.warn(`store.insert() received data for unknown model “${modelName}”.`);continue;}
const insertData=[];for(const vals of Array.isArray(data)?data:[data]){const extraFields=addFieldsByPyModel[pyOrJsModelName];if(extraFields){Object.assign(vals,extraFields);}
if(vals._DELETE){delete vals._DELETE;recordsDataToDelete.push([modelName,vals]);}else{insertData.push(vals);}}
const records=store[modelName].insert(insertData,options);if(!res[modelName]){res[modelName]=records;}else{const knownRecordIds=new Set(res[modelName].map((r)=>r.localId));res[modelName].push(...records.filter((r)=>!knownRecordIds.has(r.localId)));}}
for(const[modelName,vals]of recordsDataToDelete){store[modelName].get(vals)?.delete();}
return res;});}
async startMeeting(){const thread=await this.env.services["discuss.core.common"].createGroupChat({default_display_mode:"video_full_screen",partners_to:[this.self.id],});this.ChatWindow.get(thread)?.update({autofocus:0});this.env.services["discuss.rtc"].toggleCall(thread,{camera:true});this.openInviteThread=thread;}
tabToThreadType(tab){return tab==="chat"?["chat","group"]:[tab];}
handleClickOnLink(ev,thread){const model=ev.target.dataset.oeModel;const id=Number(ev.target.dataset.oeId);if(ev.target.closest(".o_channel_redirect")&&model&&id){ev.preventDefault();this.Thread.getOrFetch({model,id}).then((thread)=>{if(thread){thread.open();}});return true;}else if(ev.target.closest(".o_mail_redirect")&&id){ev.preventDefault();this.openChat({partnerId:id});return true;}
return false;}
setup(){super.setup();this._fetchDataDebounced=debounce(this._fetchDataDebounced,Store.FETCH_DATA_DEBOUNCE_DELAY);this.updateBusSubscription=debounce(()=>this.env.services.bus_service.forceUpdateChannels(),0);}
onStarted(){navigator.serviceWorker?.addEventListener("message",({data={}})=>{const{type,payload}=data;if(type==="notification-display-request"){const{correlationId,model,res_id}=payload;const thread=this.Thread.get({model,id:res_id});let isTabFocused;try{isTabFocused=parent.document.hasFocus();}catch{}
const isInbox=this.store.self.notification_preference==="inbox"&&model!=="discuss.channel";if((isTabFocused&&thread?.isDisplayed)||isInbox){navigator.serviceWorker.controller?.postMessage({type:"notification-display-response",payload:{correlationId},});}}});}
async getChat({userId,partnerId}){const partner=await this.getPartner({userId,partnerId});let chat=partner?.searchChat();if(!chat||!chat.is_pinned){chat=await this.joinChat(partnerId||partner?.id);}
if(!chat){this.env.services.notification.add(_t("An unexpected error occurred during the creation of the chat."),{type:"warning"});return;}
return chat;}
getLastMessageId(){return Object.values(this.Message.records).reduce((lastMessageId,message)=>Math.max(lastMessageId,message.id),0);}
getMentionsFromText(body,{mentionedChannels=[],mentionedPartners=[],specialMentions=[]}={}){const validMentions={};validMentions.threads=mentionedChannels.filter((thread)=>{if(thread.parent_channel_id){return body.includes(`#${thread.parent_channel_id.displayName} > ${thread.displayName}`);}
return body.includes(`#${thread.displayName}`);});validMentions.partners=mentionedPartners.filter((partner)=>body.includes(`@${partner.name}`));validMentions.specialMentions=this.specialMentions.filter((special)=>body.includes(`@${special.label}`)).map((special)=>special.label);return validMentions;}
async getMessagePostParams({body,postData,thread}){const{attachments,cannedResponseIds,emailAddSignature,isNote,mentionedChannels,mentionedPartners,}=postData;const subtype=isNote?"mail.mt_note":"mail.mt_comment";const validMentions=this.getMentionsFromText(body,{mentionedChannels,mentionedPartners,});const partner_ids=validMentions?.partners.map((partner)=>partner.id)??[];const recipientEmails=[];const recipientAdditionalValues={};if(!isNote){const recipientIds=thread.suggestedRecipients.filter((recipient)=>recipient.persona&&recipient.checked).map((recipient)=>recipient.persona.id);thread.suggestedRecipients.filter((recipient)=>recipient.checked&&!recipient.persona).forEach((recipient)=>{recipientEmails.push(recipient.email);recipientAdditionalValues[recipient.email]=recipient.create_values;});partner_ids.push(...recipientIds);}
postData={body:await prettifyMessageContent(body,validMentions),email_add_signature:emailAddSignature,message_type:"comment",subtype_xmlid:subtype,};if(attachments.length){postData.attachment_ids=attachments.map(({id})=>id);}
if(partner_ids.length){Object.assign(postData,{partner_ids});}
if(thread.model==="discuss.channel"&&validMentions?.specialMentions.length){postData.special_mentions=validMentions.specialMentions;}
const params={context:{mail_post_autofollow:!isNote&&thread.hasWriteAccess,},post_data:postData,thread_id:thread.id,thread_model:thread.model,};if(attachments.length){params.attachment_tokens=attachments.map((attachment)=>attachment.access_token);}
if(cannedResponseIds?.length){params.canned_response_ids=cannedResponseIds;}
if(recipientEmails.length){Object.assign(params,{partner_emails:recipientEmails,partner_additional_values:recipientAdditionalValues,});}
return params;}
getNextTemporaryId(){const lastMessageId=this.getLastMessageId();if(prevLastMessageId===lastMessageId){temporaryIdOffset+=0.01;}else{prevLastMessageId=lastMessageId;temporaryIdOffset=0.01;}
return lastMessageId+temporaryIdOffset;}
async getPartner({userId,partnerId}){if(userId){let user=this.users[userId];if(!user){this.users[userId]={id:userId};user=this.users[userId];}
if(!user.partner_id){const[userData]=await this.env.services.orm.silent.read("res.users",[user.id],["partner_id"],{context:{active_test:false}});if(userData){user.partner_id=userData.partner_id[0];}}
if(!user.partner_id){this.env.services.notification.add(_t("You can only chat with existing users."),{type:"warning",});return;}
partnerId=user.partner_id;}
if(partnerId){const partner=this.Persona.insert({id:partnerId,type:"partner"});if(!partner.userId){const[userId]=await this.env.services.orm.silent.search("res.users",[["partner_id","=",partnerId]],{context:{active_test:false}});if(!userId){this.env.services.notification.add(_t("You can only chat with partners that have a dedicated user."),{type:"info"});return;}
partner.userId=userId;}
return partner;}}
getRecentChatPartnerIds(){return Object.values(this.Thread.records).filter((thread)=>thread.channel_type==="chat"&&thread.correspondent).sort((a,b)=>compareDatetime(b.lastInterestDt,a.lastInterestDt)||b.id-a.id).map((thread)=>thread.correspondent.persona.id);}
async joinChannel(id,name){await this.env.services.orm.call("discuss.channel","add_members",[[id]],{partner_ids:[this.self.id],});const thread=this.Thread.insert({channel_type:"channel",id,model:"discuss.channel",name,});if(!thread.avatarCacheKey){thread.avatarCacheKey="hello";}
thread.open();return thread;}
async joinChat(id,forceOpen=false){const data=await this.env.services.orm.call("discuss.channel","channel_get",[],{partners_to:[id],force_open:forceOpen,});const{Thread}=this.store.insert(data);return Thread[0];}
async openChat(person){const chat=await this.getChat(person);chat?.open();}
openDocument({id,model}){this.env.services.action.doAction({type:"ir.actions.act_window",res_model:model,views:[[false,"form"]],res_id:id,});}
openNewMessage(){let cw=this.ChatWindow.get({thread:undefined});if(cw){cw.focus();return;}
cw=this.ChatWindow.insert({thread:undefined,fromMessagingMenu:true});this.chatHub.opened.unshift(cw);cw.focus();}
async search(searchTerm,thread,before=false){const{count,data,messages}=await rpc(thread.getFetchRoute(),{...thread.getFetchParams(),search_term:await prettifyMessageContent(searchTerm),before,});this.insert(data,{html:true});return{count,loadMore:messages.length===this.FETCH_LIMIT,messages:this.Message.insert(messages),};}
async searchPartners(searchStr="",limit=10){const partners=[];const searchTerm=cleanTerm(searchStr);for(const localId in this.Persona.records){const persona=this.Persona.records[localId];if(persona.type!=="partner"){continue;}
const partner=persona;if(partner.name&&cleanTerm(partner.name).includes(searchTerm)&&((partner.active&&partner.userId)||partner===this.store.odoobot)){partners.push(partner);if(partners.length>=limit){break;}}}
if(!partners.length){const data=await this.env.services.orm.silent.call("res.partner","im_search",[searchTerm,limit,]);const{Persona=[]}=this.store.insert(data);partners.push(...Persona);}
return partners;}}
Store.register();const storeService=__exports.storeService={dependencies:["bus_service","im_status","ui"],start(env,services){const store=makeStore(env);store.insert(session.storeData,{html:true});store.self??={id:-1,type:"guest"};store.settings??={};store.initialize();store.onStarted();return store;},};registry.category("services").add("mail.store",storeService);return __exports;});;

/* /mail/static/src/core/common/suggestion_hook.js */
odoo.define('@mail/core/common/suggestion_hook',['@odoo/owl','@web/core/network/rpc','@web/core/utils/hooks','@web/core/utils/timing'],function(require){'use strict';let __exports={};const{status,useComponent,useEffect,useState}=require("@odoo/owl");const{ConnectionAbortedError}=require("@web/core/network/rpc");const{useService}=require("@web/core/utils/hooks");const{useDebounced}=require("@web/core/utils/timing");const UseSuggestion=__exports.UseSuggestion=class UseSuggestion{constructor(comp){this.comp=comp;this.fetchSuggestions=useDebounced(this.fetchSuggestions.bind(this),250);useEffect(()=>{this.update();if(this.search.position===undefined||!this.search.delimiter){return;}
if(this.composer.store.self.type!=="partner"){return;}
if(this.lastFetchedSearch?.count===0&&(!this.search.delimiter||this.isSearchMoreSpecificThanLastFetch)){return;}
this.fetchSuggestions();},()=>[this.search.delimiter,this.search.position,this.search.term]);useEffect(()=>{this.detect();},()=>[this.composer.selection.start,this.composer.selection.end,this.composer.text]);}
comp;get composer(){return this.comp.props.composer;}
suggestionService=useService("mail.suggestion");state=useState({count:0,items:undefined,isFetching:false,});search={delimiter:undefined,position:undefined,term:"",};lastFetchedSearch;get isSearchMoreSpecificThanLastFetch(){return(this.lastFetchedSearch.delimiter===this.search.delimiter&&this.search.term.startsWith(this.lastFetchedSearch.term)&&this.lastFetchedSearch.position>=this.search.position);}
clearRawMentions(){this.composer.mentionedChannels.length=0;this.composer.mentionedPartners.length=0;}
clearCannedResponses(){this.composer.cannedResponses=[];}
clearSearch(){Object.assign(this.search,{delimiter:undefined,position:undefined,term:"",});this.state.items=undefined;}
detect(){const{start,end}=this.composer.selection;const text=this.composer.text;if(start!==end){this.clearSearch();return;}
const candidatePositions=[];let numberOfSpaces=0;for(let index=start-1;index>=0;--index){if(/\s/.test(text[index])){numberOfSpaces++;if(numberOfSpaces===2){break;}}
candidatePositions.push(index);}
if(this.search.position!==undefined&&this.search.position<start){candidatePositions.push(this.search.position);}
const supportedDelimiters=this.suggestionService.getSupportedDelimiters(this.thread);for(const candidatePosition of candidatePositions){if(candidatePosition<0||candidatePosition>=text.length){continue;}
const candidateChar=text[candidatePosition];if(!supportedDelimiters.find(([delimiter,allowedPosition])=>delimiter===candidateChar&&(allowedPosition===undefined||allowedPosition===candidatePosition))){continue;}
const charBeforeCandidate=text[candidatePosition-1];if(charBeforeCandidate&&!/\s/.test(charBeforeCandidate)){continue;}
Object.assign(this.search,{delimiter:candidateChar,position:candidatePosition,term:text.substring(candidatePosition+1,start),});this.state.count++;return;}
this.clearSearch();}
get thread(){return this.composer.thread||this.composer.message?.thread;}
insert(option){const position=this.composer.selection.start;const text=this.composer.text;let before=text.substring(0,this.search.position+1);let after=text.substring(position,text.length);if(this.search.delimiter===":"){before=text.substring(0,this.search.position);after=text.substring(position,text.length);}
if(option.partner){this.composer.mentionedPartners.add({id:option.partner.id,type:"partner",});}
if(option.thread){this.composer.mentionedChannels.add({model:"discuss.channel",id:option.thread.id,});}
if(option.cannedResponse){this.composer.cannedResponses.push(option.cannedResponse);}
this.clearSearch();this.composer.text=before+option.label+" "+after;this.composer.selection.start=before.length+option.label.length+1;this.composer.selection.end=before.length+option.label.length+1;this.composer.forceCursorMove=true;}
update(){if(!this.search.delimiter){return;}
const{type,suggestions}=this.suggestionService.searchSuggestions(this.search,{thread:this.thread,sort:true,});if(!suggestions.length){this.state.items=undefined;return;}
const limit=8;suggestions.length=Math.min(suggestions.length,limit);this.state.items={type,suggestions};}
async fetchSuggestions(){if(!this.thread||status(this.comp)==="destroyed"){return;}
let resetFetchingState=true;try{this.abortController?.abort();this.abortController=new AbortController();this.state.isFetching=true;await this.suggestionService.fetchSuggestions(this.search,{thread:this.thread,abortSignal:this.abortController.signal,});}catch(e){this.lastFetchedSearch=null;if(e instanceof ConnectionAbortedError){resetFetchingState=false;return;}
throw e;}finally{if(resetFetchingState){this.state.isFetching=false;}}
if(!this.thread||status(this.comp)==="destroyed"){return;}
this.update();this.lastFetchedSearch={...this.search,count:this.state.items?.suggestions.length??0,};if(!this.state.items?.suggestions.length){this.clearSearch();}}}
__exports.useSuggestion=useSuggestion;function useSuggestion(){return new UseSuggestion(useComponent());}
return __exports;});;

/* /mail/static/src/core/common/suggestion_service.js */
odoo.define('@mail/core/common/suggestion_service',['@mail/core/common/partner_compare','@mail/utils/common/format','@odoo/owl','@web/core/registry'],function(require){'use strict';let __exports={};const{partnerCompareRegistry}=require("@mail/core/common/partner_compare");const{cleanTerm}=require("@mail/utils/common/format");const{toRaw}=require("@odoo/owl");const{registry}=require("@web/core/registry");const SuggestionService=__exports.SuggestionService=class SuggestionService{constructor(env,services){this.env=env;this.orm=services.orm;this.store=services["mail.store"];}
getSupportedDelimiters(thread){return[["@"],["#"],[":"]];}
async fetchSuggestions({delimiter,term},{thread,abortSignal}={}){const cleanedSearchTerm=cleanTerm(term);switch(delimiter){case"@":{await this.fetchPartners(cleanedSearchTerm,thread,{abortSignal});break;}
case"#":await this.fetchThreads(cleanedSearchTerm,{abortSignal});break;case":":await this.store.cannedReponses.fetch();break;}}
makeOrmCall(model,method,args,kwargs,{abortSignal}={}){return new Promise((res,rej)=>{const req=this.orm.silent.call(model,method,args,kwargs);const onAbort=()=>{try{req.abort();}catch(e){rej(e);}};abortSignal?.addEventListener("abort",onAbort);req.then(res).catch(rej).finally(()=>abortSignal?.removeEventListener("abort",onAbort));});}
async fetchPartners(term,thread,{abortSignal}={}){const kwargs={search:term};if(thread?.model==="discuss.channel"){kwargs.channel_id=thread.id;}
const data=await this.makeOrmCall("res.partner",thread?.model==="discuss.channel"?"get_mention_suggestions_from_channel":"get_mention_suggestions",[],kwargs,{abortSignal});this.store.insert(data);}
async fetchThreads(term,{abortSignal}={}){const suggestedThreads=await this.makeOrmCall("discuss.channel","get_mention_suggestions",[],{search:term},{abortSignal});this.store.Thread.insert(suggestedThreads);}
searchCannedResponseSuggestions(cleanedSearchTerm,sort){const cannedResponses=Object.values(this.store["mail.canned.response"].records).filter((cannedResponse)=>cleanTerm(cannedResponse.source).includes(cleanedSearchTerm));const sortFunc=(c1,c2)=>{const cleanedName1=cleanTerm(c1.source);const cleanedName2=cleanTerm(c2.source);if(cleanedName1.startsWith(cleanedSearchTerm)&&!cleanedName2.startsWith(cleanedSearchTerm)){return-1;}
if(!cleanedName1.startsWith(cleanedSearchTerm)&&cleanedName2.startsWith(cleanedSearchTerm)){return 1;}
if(cleanedName1<cleanedName2){return-1;}
if(cleanedName1>cleanedName2){return 1;}
return c1.id-c2.id;};return{type:"mail.canned.response",suggestions:sort?cannedResponses.sort(sortFunc):cannedResponses,};}
searchSuggestions({delimiter,term},{thread,sort=false}={}){thread=toRaw(thread);const cleanedSearchTerm=cleanTerm(term);switch(delimiter){case"@":{return this.searchPartnerSuggestions(cleanedSearchTerm,thread,sort);}
case"#":return this.searchChannelSuggestions(cleanedSearchTerm,sort);case":":return this.searchCannedResponseSuggestions(cleanedSearchTerm,sort);}
return{type:undefined,suggestions:[],};}
getPartnerSuggestions(thread){let partners;const isNonPublicChannel=thread&&(thread.channel_type==="group"||thread.channel_type==="chat"||(thread.channel_type==="channel"&&(thread.parent_channel_id||thread).group_public_id));if(isNonPublicChannel){partners=thread.channelMembers.map((member)=>member.persona).filter((persona)=>persona.type==="partner");if(thread.channel_type==="channel"){const group=(thread.parent_channel_id||thread).group_public_id;partners=new Set([...partners,...(group?.personas??[])]);}}else{partners=Object.values(this.store.Persona.records).filter((persona)=>{if(thread?.model!=="discuss.channel"&&persona.eq(this.store.odoobot)){return false;}
return persona.type==="partner";});}
return partners;}
searchPartnerSuggestions(cleanedSearchTerm,thread,sort){const partners=this.getPartnerSuggestions(thread);const suggestions=[];for(const partner of partners){if(!partner.name){continue;}
if(cleanTerm(partner.name).includes(cleanedSearchTerm)||(partner.email&&cleanTerm(partner.email).includes(cleanedSearchTerm))){suggestions.push(partner);}}
suggestions.push(...this.store.specialMentions.filter((special)=>thread&&special.channel_types.includes(thread.channel_type)&&cleanedSearchTerm.length>=Math.min(4,special.label.length)&&(special.label.startsWith(cleanedSearchTerm)||cleanTerm(special.description.toString()).includes(cleanedSearchTerm))));return{type:"Partner",suggestions:sort?[...this.sortPartnerSuggestions(suggestions,cleanedSearchTerm,thread)]:suggestions,};}
sortPartnerSuggestions(partners,searchTerm="",thread=undefined){const cleanedSearchTerm=cleanTerm(searchTerm);const compareFunctions=partnerCompareRegistry.getAll();const context={recentChatPartnerIds:this.store.getRecentChatPartnerIds()};const memberPartnerIds=new Set(thread?.channelMembers.filter((member)=>member.persona.type==="partner").map((member)=>member.persona.id));return partners.sort((p1,p2)=>{p1=toRaw(p1);p2=toRaw(p2);if(p1.isSpecial||p2.isSpecial){return 0;}
for(const fn of compareFunctions){const result=fn(p1,p2,{env:this.env,memberPartnerIds,searchTerm:cleanedSearchTerm,thread,context,});if(result!==undefined){return result;}}});}
searchChannelSuggestions(cleanedSearchTerm,sort){const suggestionList=Object.values(this.store.Thread.records).filter((thread)=>thread.channel_type==="channel"&&thread.displayName&&cleanTerm(thread.displayName).includes(cleanedSearchTerm));const sortFunc=(c1,c2)=>{const isPublicChannel1=c1.channel_type==="channel"&&!c2.authorizedGroupFullName;const isPublicChannel2=c2.channel_type==="channel"&&!c2.authorizedGroupFullName;if(isPublicChannel1&&!isPublicChannel2){return-1;}
if(!isPublicChannel1&&isPublicChannel2){return 1;}
if(c1.hasSelfAsMember&&!c2.hasSelfAsMember){return-1;}
if(!c1.hasSelfAsMember&&c2.hasSelfAsMember){return 1;}
const cleanedDisplayName1=cleanTerm(c1.displayName);const cleanedDisplayName2=cleanTerm(c2.displayName);if(cleanedDisplayName1.startsWith(cleanedSearchTerm)&&!cleanedDisplayName2.startsWith(cleanedSearchTerm)){return-1;}
if(!cleanedDisplayName1.startsWith(cleanedSearchTerm)&&cleanedDisplayName2.startsWith(cleanedSearchTerm)){return 1;}
if(cleanedDisplayName1<cleanedDisplayName2){return-1;}
if(cleanedDisplayName1>cleanedDisplayName2){return 1;}
return c1.id-c2.id;};return{type:"Thread",suggestions:sort?suggestionList.sort(sortFunc):suggestionList,};}}
const suggestionService=__exports.suggestionService={dependencies:["orm","mail.store"],start(env,services){return new SuggestionService(env,services);},};registry.category("services").add("mail.suggestion",suggestionService);return __exports;});;

/* /mail/static/src/core/common/thread.js */
odoo.define('@mail/core/common/thread',['@mail/core/common/date_section','@mail/core/common/message','@mail/core/common/record','@mail/utils/common/hooks','@odoo/owl','@web/core/browser/browser','@web/core/l10n/translation','@web/core/transition','@web/core/utils/hooks','@web/core/utils/strings'],function(require){'use strict';let __exports={};const{DateSection}=require("@mail/core/common/date_section");const{Message}=require("@mail/core/common/message");const{Record}=require("@mail/core/common/record");const{useVisible}=require("@mail/utils/common/hooks");const{Component,markRaw,onMounted,onWillDestroy,onWillPatch,onWillUpdateProps,reactive,toRaw,useChildSubEnv,useEffect,useRef,useState,}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{_t}=require("@web/core/l10n/translation");const{Transition}=require("@web/core/transition");const{useBus,useRefListener,useService}=require("@web/core/utils/hooks");const{escape}=require("@web/core/utils/strings");const PRESENT_VIEWPORT_THRESHOLD=__exports.PRESENT_VIEWPORT_THRESHOLD=1;const Thread=__exports.Thread=class Thread extends Component{static components={Message,Transition,DateSection};static props=["showDates?","isInChatWindow?","jumpPresent?","thread","messageEdition?","messageToReplyTo?","order?","scrollRef?","showEmptyMessage?","showJumpPresent?","messageActions?",];static defaultProps={isInChatWindow:false,jumpPresent:0,order:"asc",showDates:true,showEmptyMessage:true,showJumpPresent:true,messageActions:true,};static template="mail.Thread";setup(){super.setup();this.escape=escape;this.registerMessageRef=this.registerMessageRef.bind(this);this.store=useState(useService("mail.store"));this.state=useState({isReplyingTo:false,mountedAndLoaded:false,showJumpPresent:false,scrollTop:null,});this.lastJumpPresent=this.props.jumpPresent;this.orm=useService("orm");this.ui=useService("ui");this.messageHighlight=this.env.messageHighlight?useState(this.env.messageHighlight):null;this.scrollingToHighlight=false;this.refByMessageId=reactive(new Map(),()=>this.scrollToHighlighted());useEffect(()=>this.scrollToHighlighted(),()=>[this.messageHighlight?.highlightedMessageId]);this.present=useRef("load-newer");this.jumpPresentRef=useRef("jump-present");this.root=useRef("messages");this.scrollableRef=this.props.scrollRef??this.root;useRefListener(this.scrollableRef,"scrollend",()=>(this.state.scrollTop=this.scrollableRef.el.scrollTop));useEffect((loadNewer,mountedAndLoaded,unreadSynced)=>{if(loadNewer||unreadSynced||!mountedAndLoaded||!this.props.thread.selfMember||!this.scrollableRef.el){return;}
const el=this.scrollableRef.el;if(Math.abs(el.scrollTop+el.clientHeight-el.scrollHeight)<=1){this.props.thread.selfMember.hideUnreadBanner=true;}},()=>[this.props.thread.loadNewer,this.state.mountedAndLoaded,this.props.thread.selfMember?.unreadSynced,this.state.scrollTop,]);this.loadOlderState=useVisible("load-older",async()=>{await this.messageHighlight?.scrollPromise;if(this.loadOlderState.isVisible){toRaw(this.props.thread).fetchMoreMessages();}},{ready:false});this.loadNewerState=useVisible("load-newer",async()=>{await this.messageHighlight?.scrollPromise;if(this.loadNewerState.isVisible){toRaw(this.props.thread).fetchMoreMessages("newer");}},{ready:false});this.presentThresholdState=useVisible("present-treshold",()=>this.updateShowJumpPresent());this.setupScroll();useEffect(()=>{this.computeJumpPresentPosition();},()=>[this.jumpPresentRef.el,this.viewportEl]);useEffect(()=>this.updateShowJumpPresent(),()=>[this.props.thread.loadNewer]);useEffect(()=>{if(this.props.jumpPresent!==this.lastJumpPresent){this.messageHighlight?.clearHighlight();if(this.props.thread.loadNewer){this.jumpToPresent();}else{if(this.props.order==="desc"){this.scrollableRef.el.scrollTop=0;}else{this.scrollableRef.el.scrollTop=this.scrollableRef.el.scrollHeight-
this.scrollableRef.el.clientHeight;}
this.props.thread.scrollTop="bottom";}
this.lastJumpPresent=this.props.jumpPresent;}},()=>[this.props.jumpPresent]);useEffect(()=>{if(this.props.thread.highlightMessage&&this.state.mountedAndLoaded){this.messageHighlight?.highlightMessage(this.props.thread.highlightMessage,this.props.thread);this.props.thread.highlightMessage=null;}},()=>[this.props.thread.highlightMessage,this.state.mountedAndLoaded]);useEffect(()=>{if(!this.state.mountedAndLoaded){return;}
this.updateShowJumpPresent();},()=>[this.state.mountedAndLoaded]);onMounted(()=>{if(!this.env.chatter||this.env.chatter?.fetchMessages){if(this.env.chatter){this.env.chatter.fetchMessages=false;}
if(this.props.thread.selfMember&&this.props.thread.scrollUnread){toRaw(this.props.thread).loadAround(this.props.thread.selfMember.new_message_separator);}else{toRaw(this.props.thread).fetchNewMessages();}}});useEffect((isLoaded)=>{this.state.mountedAndLoaded=isLoaded;},()=>[this.props.thread.isLoaded,this.state.mountedAndLoaded]);useBus(this.env.bus,"MAIL:RELOAD-THREAD",({detail})=>{const{model,id}=this.props.thread;if(detail.model===model&&detail.id===id){toRaw(this.props.thread).fetchNewMessages();}});onWillUpdateProps((nextProps)=>{if(nextProps.thread.notEq(this.props.thread)){this.lastJumpPresent=nextProps.jumpPresent;}
if(!this.env.chatter||this.env.chatter?.fetchMessages){if(this.env.chatter){this.env.chatter.fetchMessages=false;}
toRaw(nextProps.thread).fetchNewMessages();}});}
computeJumpPresentPosition(){if(!this.viewportEl||!this.jumpPresentRef.el){return;}
const width=this.viewportEl.clientWidth;const height=this.viewportEl.clientHeight;const computedStyle=window.getComputedStyle(this.viewportEl);const ps=parseInt(computedStyle.getPropertyValue("padding-left"));const pe=parseInt(computedStyle.getPropertyValue("padding-right"));const pt=parseInt(computedStyle.getPropertyValue("padding-top"));const pb=parseInt(computedStyle.getPropertyValue("padding-bottom"));this.jumpPresentRef.el.style.transform=`translate(${
            this.env.inChatter ? 22 : width - ps - pe - 22
        }px, ${
            this.env.inChatter && !this.env.inChatter.aside
                ? -22
                : height - pt - pb - (this.env.inChatter?.aside ? 75 : 0)
        }px)`;}
setupScroll(){const ref=this.scrollableRef;let lastSetValue;let loadedAndPatched=false;let snapshot;let newestPersistentMessage;let oldestPersistentMessage;let loadNewer;const reset=()=>{this.state.mountedAndLoaded=false;this.loadOlderState.ready=false;this.loadNewerState.ready=false;lastSetValue=undefined;snapshot=undefined;newestPersistentMessage=undefined;oldestPersistentMessage=undefined;loadedAndPatched=false;loadNewer=false;};let stopOnChange=Record.onChange(this.props.thread,"isLoaded",()=>{if(!this.props.thread.isLoaded||!this.state.mountedAndLoaded){reset();}});onWillUpdateProps((nextProps)=>{if(nextProps.thread.notEq(this.props.thread)){stopOnChange();stopOnChange=Record.onChange(nextProps.thread,"isLoaded",()=>{if(!nextProps.thread.isLoaded||!this.state.mountedAndLoaded){reset();}});}});onWillDestroy(()=>stopOnChange());const saveScroll=()=>{const thread=toRaw(this.props.thread);const isBottom=this.props.order==="asc"?ref.el.scrollHeight-ref.el.scrollTop-ref.el.clientHeight<30:ref.el.scrollTop<30;if(isBottom){thread.scrollTop="bottom";}else{thread.scrollTop=this.props.order==="asc"?ref.el.scrollTop:ref.el.scrollHeight-ref.el.scrollTop-ref.el.clientHeight;}};const setScroll=(value)=>{ref.el.scrollTop=value;lastSetValue=value;saveScroll();};const applyScroll=()=>{if(!this.props.thread.isLoaded||!this.state.mountedAndLoaded){reset();return;}
const thread=toRaw(this.props.thread);const olderMessages=thread.oldestPersistentMessage?.id<oldestPersistentMessage?.id;const newerMessages=thread.newestPersistentMessage?.id>newestPersistentMessage?.id;const messagesAtTop=(this.props.order==="asc"&&olderMessages)||(this.props.order==="desc"&&newerMessages);const messagesAtBottom=(this.props.order==="desc"&&olderMessages)||(this.props.order==="asc"&&newerMessages&&(loadNewer||thread.scrollTop!=="bottom"));if(thread.selfMember&&thread.scrollUnread){if(thread.firstUnreadMessage){const messageEl=this.refByMessageId.get(thread.firstUnreadMessage.id)?.el;if(!messageEl){return;}
const messageCenter=messageEl.offsetTop-
this.scrollableRef.el.offsetHeight/2+
messageEl.offsetHeight/2;setScroll(messageCenter);}else{const scrollTop=this.props.order==="asc"?this.scrollableRef.el.scrollHeight-
this.scrollableRef.el.clientHeight:0;setScroll(scrollTop);}
thread.scrollUnread=false;}else if(snapshot&&messagesAtTop){setScroll(snapshot.scrollTop+ref.el.scrollHeight-snapshot.scrollHeight);}else if(snapshot&&messagesAtBottom){setScroll(snapshot.scrollTop);}else if(!this.scrollingToHighlight&&!this.env.messageHighlight?.highlightedMessageId&&thread.scrollTop!==undefined){let value;if(thread.scrollTop==="bottom"){value=this.props.order==="asc"?ref.el.scrollHeight-ref.el.clientHeight:0;}else{value=this.props.order==="asc"?thread.scrollTop:ref.el.scrollHeight-thread.scrollTop-ref.el.clientHeight;}
if(lastSetValue===undefined||Math.abs(lastSetValue-value)>1){setScroll(value);}}
snapshot=undefined;newestPersistentMessage=thread.newestPersistentMessage;oldestPersistentMessage=thread.oldestPersistentMessage;loadNewer=thread.loadNewer;if(!loadedAndPatched){loadedAndPatched=true;this.loadOlderState.ready=true;this.loadNewerState.ready=true;}};onWillPatch(()=>{if(!loadedAndPatched){return;}
snapshot={scrollHeight:ref.el.scrollHeight,scrollTop:ref.el.scrollTop,};});useEffect(applyScroll);useChildSubEnv({onImageLoaded:applyScroll,});const observer=new ResizeObserver(()=>{this.computeJumpPresentPosition();applyScroll();});useEffect((el,mountedAndLoaded)=>{if(el&&mountedAndLoaded){el.addEventListener("scroll",saveScroll);observer.observe(el);return()=>{observer.unobserve(el);el.removeEventListener("scroll",saveScroll);};}},()=>[ref.el,this.state.mountedAndLoaded]);}
get viewportEl(){let viewportEl=this.scrollableRef.el;if(viewportEl&&viewportEl.clientHeight>browser.innerHeight){while(viewportEl&&viewportEl.clientHeight>browser.innerHeight){viewportEl=viewportEl.parentElement;}}
return viewportEl;}
get PRESENT_THRESHOLD(){const threshold=(this.viewportEl?.clientHeight??0)*PRESENT_VIEWPORT_THRESHOLD;return this.state.showJumpPresent?threshold-200:threshold;}
get newMessageBannerText(){if(this.props.thread.selfMember?.totalUnreadMessageCounter>1){return _t("%s new messages",this.props.thread.selfMember.totalUnreadMessageCounter);}
return _t("1 new message");}
get preferenceButtonText(){const[,before,inside,after]=_t("<button>Change your preferences</button> to receive new notifications in your inbox.").match(/(.*)<button>(.*)<\/button>(.*)/)??[];return{before,inside,after};}
updateShowJumpPresent(){this.state.showJumpPresent=this.props.thread.loadNewer||this.presentThresholdState.isVisible===false;}
onClickLoadOlder(){this.props.thread.fetchMoreMessages();}
async onClickPreferences(){const actionDescription=await this.orm.call("res.users","action_get");actionDescription.res_id=this.store.self.userId;this.env.services.action.doAction(actionDescription);}
async onParentMessageClick(parentMessage){if(!parentMessage){return;}
const targetThread=parentMessage.thread;if(!targetThread){return;}
if(targetThread.eq(this.props.thread)){this.env.messageHighlight?.highlightMessage(parentMessage,targetThread);}else{targetThread.highlightMessage=parentMessage;await targetThread.open({focus:true});}}
getMessageClassName(message){return!message.isNotification&&this.messageHighlight?.highlightedMessageId===message.id?"o-highlighted bg-view shadow-lg pb-1":"";}
async jumpToPresent(){this.messageHighlight?.clearHighlight();await this.props.thread.loadAround();this.props.thread.loadNewer=false;this.props.thread.scrollTop="bottom";this.state.showJumpPresent=false;this.scrollingToHighlight=false;if(!this.ui.isSmall){this.props.thread.composer.autofocus++;}}
async onClickUnreadMessagesBanner(){await this.props.thread.loadAround(this.props.thread.selfMember.localNewMessageSeparator);this.messageHighlight?.highlightMessage(this.props.thread.firstUnreadMessage,this.props.thread);}
registerMessageRef(message,ref){if(!ref){this.refByMessageId.delete(message.id);return;}
this.refByMessageId.set(message.id,markRaw(ref));}
isSquashed(msg,prevMsg){if(this.props.thread.model==="mail.box"){return false;}
if(!prevMsg||prevMsg.message_type==="notification"||prevMsg.isEmpty||this.env.inChatter){return false;}
if(!msg.author?.eq(prevMsg.author)){return false;}
if(!msg.thread?.eq(prevMsg.thread)){return false;}
if(msg.is_note){return false;}
return msg.datetime.ts-prevMsg.datetime.ts<5*60*1000;}
scrollToHighlighted(){if(!this.messageHighlight?.highlightedMessageId||this.scrollingToHighlight){return;}
const el=this.refByMessageId.get(this.messageHighlight.highlightedMessageId)?.el;if(el){this.scrollingToHighlight=true;this.messageHighlight.scrollTo(el).then(()=>(this.scrollingToHighlight=false));}}
get orderedMessages(){return this.props.order==="asc"?[...this.props.thread.nonEmptyMessages]:[...this.props.thread.nonEmptyMessages].reverse();}}
return __exports;});;

/* /mail/static/src/core/common/thread_actions.js */
odoo.define('@mail/core/common/thread_actions',['@odoo/owl','@web/core/l10n/translation','@web/core/registry','@mail/core/common/search_messages_panel'],function(require){'use strict';let __exports={};const{useSubEnv,useComponent,useState}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{SearchMessagesPanel}=require("@mail/core/common/search_messages_panel");const threadActionsRegistry=__exports.threadActionsRegistry=registry.category("mail.thread/actions");threadActionsRegistry.add("fold-chat-window",{condition(component){return(component.props.chatWindow&&component.props.chatWindow.thread&&(component.env.services["im_livechat.livechat"]||!component.env.services.ui.isSmall));},icon:"fa fa-fw fa-minus",name(component){return!component.props.chatWindow?.isOpen?_t("Open"):_t("Fold");},open(component){component.toggleFold();},displayActive(component){return!component.props.chatWindow?.isOpen;},sequence:99,sequenceQuick:20,}).add("rename-thread",{condition(component){return(component.thread&&component.props.chatWindow?.isOpen&&(component.thread.is_editable||component.thread.channel_type==="chat"));},icon:"fa fa-fw fa-pencil",name:_t("Rename Thread"),open(component){component.state.editingName=true;},sequence:30,sequenceGroup:20,}).add("close",{condition(component){return component.props.chatWindow;},icon:"oi fa-fw oi-close",name:_t("Close Chat Window (ESC)"),open(component){component.close();},sequence:100,sequenceQuick:10,}).add("search-messages",{component:SearchMessagesPanel,condition(component){return(["discuss.channel","mail.box"].includes(component.thread?.model)&&(!component.props.chatWindow||component.props.chatWindow.isOpen));},panelOuterClass:"o-mail-SearchMessagesPanel bg-inherit",icon:"oi oi-fw oi-search",iconLarge:"oi oi-fw fa-lg oi-search",name:_t("Search Messages"),nameActive:_t("Close Search"),sequence:20,sequenceGroup:20,setup(action){useSubEnv({searchMenu:{open:()=>action.open(),close:()=>{if(action.isActive){action.close();}},},});},toggle:true,});function transformAction(component,id,action){return{close(){if(this.toggle){component.threadActions.activeAction=component.threadActions.actionStack.pop();}
action.close?.(component,this);},component:action.component,get componentCondition(){return this.isActive&&this.component&&this.condition&&!this.popover;},get componentProps(){return action.componentProps?.(this,component);},get condition(){return threadActionsInternal.condition(component,id,action);},get disabledCondition(){return action.disabledCondition?.(component);},get icon(){return typeof action.icon==="function"?action.icon(component):action.icon;},get iconLarge(){return typeof action.iconLarge==="function"?action.iconLarge(component):action.iconLarge??action.icon;},id,get isActive(){return id===component.threadActions.activeAction?.id;},get name(){const res=this.isActive&&action.nameActive?action.nameActive:action.name;return typeof res==="function"?res(component):res;},onSelect({keepPrevious}={}){if(this.toggle&&this.isActive){this.close();}else{this.open({keepPrevious});}},open({keepPrevious}={}){if(this.toggle){if(component.threadActions.activeAction){if(keepPrevious){component.threadActions.actionStack.push(component.threadActions.activeAction);}else{component.threadActions.activeAction.close();}}
component.threadActions.activeAction=this;}
action.open?.(component,this);},get panelOuterClass(){return typeof action.panelOuterClass==="function"?action.panelOuterClass(component):action.panelOuterClass;},popover:null,get sequence(){return typeof action.sequence==="function"?action.sequence(component):action.sequence;},get sequenceGroup(){return typeof action.sequenceGroup==="function"?action.sequenceGroup(component):action.sequenceGroup;},get sequenceQuick(){return typeof action.sequenceQuick==="function"?action.sequenceQuick(component):action.sequenceQuick;},setup:action.setup,text:action.text,toggle:action.toggle,};}
const threadActionsInternal=__exports.threadActionsInternal={condition(component,id,action){if(action.condition===undefined){return true;}
return action.condition(component);},};__exports.useThreadActions=useThreadActions;function useThreadActions(){const component=useComponent();const transformedActions=threadActionsRegistry.getEntries().map(([id,action])=>transformAction(component,id,action));for(const action of transformedActions){if(action.setup){action.setup(action);}}
const state=useState({get actions(){return transformedActions.filter((action)=>action.condition).sort((a1,a2)=>a1.sequence-a2.sequence);},get partition(){const actions=transformedActions.filter((action)=>action.condition);const quick=actions.filter((a)=>a.sequenceQuick).sort((a1,a2)=>a1.sequenceQuick-a2.sequenceQuick);const grouped=actions.filter((a)=>a.sequenceGroup);const groups={};for(const a of grouped){if(!(a.sequenceGroup in groups)){groups[a.sequenceGroup]=[];}
groups[a.sequenceGroup].push(a);}
const sortedGroups=Object.entries(groups).sort(([groupId1],[groupId2])=>groupId1-groupId2);for(const[,actions]of sortedGroups){actions.sort((a1,a2)=>a1.sequence-a2.sequence);}
const group=sortedGroups.map(([groupId,actions])=>actions);const other=actions.filter((a)=>!a.sequenceQuick&!a.sequenceGroup).sort((a1,a2)=>a1.sequence-a2.sequence);return{quick,group,other};},actionStack:[],activeAction:null,});return state;}
return __exports;});;

/* /mail/static/src/core/common/thread_icon.js */
odoo.define('@mail/core/common/thread_icon',['@web/core/utils/hooks','@odoo/owl','@mail/core/common/thread_model','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{useService}=require("@web/core/utils/hooks");const{Component,useState}=require("@odoo/owl");const{Thread}=require("@mail/core/common/thread_model");const{_t}=require("@web/core/l10n/translation");const ThreadIcon=__exports.ThreadIcon=class ThreadIcon extends Component{static template="mail.ThreadIcon";static props={thread:{type:Thread},size:{optional:true,validate:(size)=>["small","medium","large"].includes(size)},className:{type:String,optional:true},};static defaultProps={size:"medium",className:"",};setup(){super.setup();this.store=useState(useService("mail.store"));}
get correspondent(){return this.props.thread.correspondent;}
get defaultChatIcon(){return{class:"fa fa-question-circle opacity-75",title:_t("No IM status available"),};}}
return __exports;});;

/* /mail/static/src/core/common/thread_model.js */
odoo.define('@mail/core/common/thread_model',['@mail/core/common/record','@mail/utils/common/format','@mail/utils/common/misc','@web/core/network/rpc','@web/core/l10n/translation','@web/core/l10n/utils','@web/core/user','@web/core/utils/concurrency','@web/core/browser/feature_detection'],function(require){'use strict';let __exports={};const{AND,Record}=require("@mail/core/common/record");const{prettifyMessageContent}=require("@mail/utils/common/format");const{assignDefined,compareDatetime,nearestGreaterThanOrEqual}=require("@mail/utils/common/misc");const{rpc}=require("@web/core/network/rpc");const{_t}=require("@web/core/l10n/translation");const{formatList}=require("@web/core/l10n/utils");const{user}=require("@web/core/user");const{Deferred}=require("@web/core/utils/concurrency");const{isMobileOS}=require("@web/core/browser/feature_detection");const Thread=__exports.Thread=class Thread extends Record{static id=AND("model","id");static records={};static get(data){return super.get(data);}
static localIdToActiveId(localId){if(!localId){return undefined;}
return localId.split(",").slice(1).join("_").replace(" AND ","_");}
static insert(data){return super.insert(...arguments);}
static new(){const thread=super.new(...arguments);Record.onChange(thread,["state"],()=>{if(thread.state==="folded"||(thread.state==="open"&&this.store.env.services.ui.isSmall&&this.store.env.services["im_livechat.livechat"])){const cw=this.store.ChatWindow?.insert({thread});thread.store.chatHub.folded.delete(cw);thread.store.chatHub.folded.unshift(cw);}
if(thread.state==="open"&&!this.store.env.services.ui.isSmall){const cw=this.store.ChatWindow?.insert({thread});thread.store.chatHub.opened.delete(cw);thread.store.chatHub.opened.unshift(cw);}});return thread;}
static async getOrFetch(data){return this.get(data);}
id;uuid;model;allMessages=Record.many("Message",{inverse:"thread",});areAttachmentsLoaded=false;group_public_id=Record.one("res.groups");attachments=Record.many("Attachment",{sort:(a1,a2)=>(a1.id<a2.id?1:-1),});get canLeave(){return(["channel","group"].includes(this.channel_type)&&!this.message_needaction_counter&&!this.group_based_subscription&&this.store.self?.type==="partner");}
get canUnpin(){return this.channel_type==="chat"&&this.importantCounter===0;}
can_react=true;channelMembers=Record.many("ChannelMember",{inverse:"thread",onDelete:(r)=>r.delete(),sort:(m1,m2)=>m1.id-m2.id,});get membersThatCanSeen(){return this.channelMembers;}
typingMembers=Record.many("ChannelMember",{inverse:"threadAsTyping"});otherTypingMembers=Record.many("ChannelMember",{compute(){return this.typingMembers.filter((member)=>!member.persona?.eq(this.store.self));},});hasOtherMembersTyping=Record.attr(false,{compute(){return this.otherTypingMembers.length>0;},});toggleBusSubscription=Record.attr(false,{compute(){return(this.model==="discuss.channel"&&this.selfMember?.memberSince>=this.store.env.services.bus_service.startedAt);},onUpdate(){this.store.updateBusSubscription();},});invitedMembers=Record.many("ChannelMember");composer=Record.one("Composer",{compute:()=>({}),inverse:"thread",onDelete:(r)=>r.delete(),});correspondent=Record.one("ChannelMember",{compute(){return this.computeCorrespondent();},});correspondentCountry=Record.one("Country",{compute(){return this.correspondent?.persona?.country??this.anonymous_country;},});get showCorrespondentCountry(){return(this.channel_type==="livechat"&&this.operator?.eq(this.store.self)&&Boolean(this.correspondentCountry));}
counter=0;counter_bus_id=0;custom_channel_name;description;displayToSelf=Record.attr(false,{compute(){return(this.is_pinned||(["channel","group"].includes(this.channel_type)&&this.hasSelfAsMember&&!this.parent_channel_id));},onUpdate(){this.onPinStateUpdated();},});followers=Record.many("Follower",{onAdd(r){r.thread=this;},onDelete:(r)=>r.delete(),});selfFollower=Record.one("Follower",{onAdd(r){r.thread=this;},onDelete:(r)=>r.delete(),});followersCount;loadOlder=false;loadNewer=false;get importantCounter(){if(this.model==="mail.box"){return this.counter;}
if(this.isChatChannel&&this.selfMember?.message_unread_counter){return this.selfMember.totalUnreadMessageCounter;}
return this.message_needaction_counter;}
isCorrespondentOdooBot=Record.attr(undefined,{compute(){return this.correspondent?.persona.eq(this.store.odoobot);},});isDisplayed=Record.attr(false,{compute(){return this.computeIsDisplayed();},onUpdate(){if(this.selfMember&&!this.isDisplayed){this.selfMember.syncUnread=true;}},});isLoadingAttachments=false;isLoadedDeferred=new Deferred();isLoaded=Record.attr(false,{onUpdate(){if(this.isLoaded){this.isLoadedDeferred.resolve();}else{const def=this.isLoadedDeferred;this.isLoadedDeferred=new Deferred();this.isLoadedDeferred.then(()=>def.resolve());}},});is_pinned=Record.attr(undefined,{onUpdate(){this.onPinStateUpdated();},});mainAttachment=Record.one("Attachment");memberCount=0;message_needaction_counter=0;message_needaction_counter_bus_id=0;messages=Record.many("Message");modelName;module_icon;pendingNewMessages=Record.many("Message");needactionMessages=Record.many("Message",{inverse:"threadAsNeedaction",sort:(message1,message2)=>message1.id-message2.id,});name;portal_partner=Record.one("Persona");selfMember=Record.one("ChannelMember",{inverse:"threadAsSelf",});state;status="new";scrollTop="bottom";transientMessages=Record.many("Message");defaultDisplayMode;scrollUnread=true;suggestedRecipients=Record.attr([],{onUpdate(){for(const recipient of this.suggestedRecipients){if(recipient.checked===undefined){recipient.checked=true;}
recipient.persona=recipient.partner_id?{type:"partner",id:recipient.partner_id}:false;}},});hasLoadingFailed=false;hasLoadingFailedError;canPostOnReadonly;last_interest_dt=Record.attr(undefined,{type:"datetime"});lastInterestDt=Record.attr(undefined,{type:"datetime",compute(){const selfMemberLastInterestDt=this.selfMember?.last_interest_dt;const lastInterestDt=this.last_interest_dt;return compareDatetime(selfMemberLastInterestDt,lastInterestDt)>0?selfMemberLastInterestDt:lastInterestDt;},});is_editable;custom_notifications=false;mute_until_dt=Record.attr(undefined,{type:"datetime"});isLocallyPinned=Record.attr(false,{onUpdate(){this.onPinStateUpdated();},});fetchMembersState="not_fetched";highlightMessage=Record.one("Message",{onAdd(msg){if(!msg.thread){msg.thread=this;}},});access_token;hash;pid;get accessRestrictedToGroupText(){if(!this.authorizedGroupFullName){return false;}
return _t('Access restricted to group "%(groupFullName)s"',{groupFullName:this.authorizedGroupFullName,});}
get areAllMembersLoaded(){return this.memberCount===this.channelMembers.length;}
get busChannel(){return`${this.model}_${this.id}`;}
get followersFullyLoaded(){return(this.followersCount===(this.selfFollower?this.followers.length+1:this.followers.length));}
get attachmentsInWebClientView(){const attachments=this.attachments.filter((attachment)=>(attachment.isPdf||attachment.isImage)&&!attachment.uploading);attachments.sort((a1,a2)=>a2.id-a1.id);return attachments;}
get isUnread(){return this.selfMember?.message_unread_counter>0||this.needactionMessages.length>0;}
get isMuted(){return this.mute_until_dt||this.store.settings.mute_until_dt;}
get typesAllowingCalls(){return["chat","channel","group"];}
get allowCalls(){return(this.typesAllowingCalls.includes(this.channel_type)&&!this.correspondent?.persona.eq(this.store.odoobot));}
get canPostMessage(){return this.hasWriteAccess||(this.hasReadAccess&&this.canPostOnReadonly);}
get hasAttachmentPanel(){return this.model==="discuss.channel";}
get isChatChannel(){return["chat","group"].includes(this.channel_type);}
get supportsCustomChannelName(){return this.isChatChannel&&this.channel_type!=="group";}
get displayName(){if(this.supportsCustomChannelName&&this.custom_channel_name){return this.custom_channel_name;}
if(this.channel_type==="chat"&&this.correspondent){return this.correspondent.persona.name;}
if(this.channel_type==="group"&&!this.name){return formatList(this.channelMembers.map((channelMember)=>channelMember.persona.name));}
return this.name;}
get correspondents(){return this.channelMembers.filter(({persona})=>persona?.notEq(this.store.self));}
computeCorrespondent(){if(["channel","group"].includes(this.channel_type)){return undefined;}
const correspondents=this.correspondents;if(correspondents.length===1){return correspondents[0];}
if(correspondents.length===0&&this.channelMembers.length===1){return this.channelMembers[0];}
return undefined;}
computeIsDisplayed(){return this.store.ChatWindow.get({thread:this})?.isOpen;}
get avatarUrl(){return this.module_icon??this.store.DEFAULT_AVATAR;}
get allowDescription(){return["channel","group"].includes(this.channel_type);}
get isTransient(){return!this.id||this.id<0;}
get lastEditableMessageOfSelf(){const editableMessagesBySelf=this.nonEmptyMessages.filter((message)=>message.isSelfAuthored&&message.editable);if(editableMessagesBySelf.length>0){return editableMessagesBySelf.at(-1);}
return null;}
get needactionCounter(){return this.isChatChannel?this.selfMember?.message_unread_counter??0:this.message_needaction_counter;}
newestMessage=Record.one("Message",{inverse:"threadAsNewest",compute(){return this.messages.findLast((msg)=>!msg.isEmpty);},});firstUnreadMessage=Record.one("Message",{compute(){if(!this.selfMember){return null;}
const messages=this.nonEmptyMessages;const separator=this.selfMember.localNewMessageSeparator;if(separator===0&&!this.loadOlder){return messages[0];}
if(!separator||messages.length===0||messages.at(-1).id<separator){return null;}
let message=this.store.Message.get({id:separator});if(!message||this.notEq(message.thread)||message.isEmpty){message=nearestGreaterThanOrEqual(messages,separator,(msg)=>msg.id);}
return message;},});get newestPersistentMessage(){return this.messages.findLast((msg)=>Number.isInteger(msg.id));}
newestPersistentAllMessages=Record.many("Message",{compute(){const allPersistentMessages=this.allMessages.filter((message)=>Number.isInteger(message.id));allPersistentMessages.sort((m1,m2)=>m2.id-m1.id);return allPersistentMessages;},});newestPersistentOfAllMessage=Record.one("Message",{compute(){return this.newestPersistentAllMessages[0];},});newestPersistentNotEmptyOfAllMessage=Record.one("Message",{compute(){return this.newestPersistentAllMessages.find((message)=>!message.isEmpty);},});get oldestPersistentMessage(){return this.messages.find((msg)=>Number.isInteger(msg.id));}
onPinStateUpdated(){}
get hasSelfAsMember(){return Boolean(this.selfMember);}
hasSeenFeature=Record.attr(false,{compute(){return this.store.channel_types_with_seen_infos.includes(this.channel_type);},});get invitationLink(){if(!this.uuid||this.channel_type==="chat"){return undefined;}
return`${window.location.origin}/chat/${this.id}/${this.uuid}`;}
get isEmpty(){return!this.messages.some((message)=>!message.isEmpty);}
get nonEmptyMessages(){return this.messages.filter((message)=>!message.isEmpty);}
get persistentMessages(){return this.messages.filter((message)=>!message.is_transient);}
get prefix(){return this.isChatChannel?"@":"#";}
get showUnreadBanner(){return(!this.selfMember?.hideUnreadBanner&&this.selfMember?.localMessageUnreadCounter>0&&this.firstUnreadMessage);}
get rpcParams(){return{};}
lastMessageSeenByAllId=Record.attr(undefined,{compute(){if(!this.hasSeenFeature){return;}
const otherMembers=this.channelMembers.filter((member)=>member.notEq(this.selfMember));if(otherMembers.length===0){return;}
const otherLastSeenMessageIds=otherMembers.filter((member)=>member.seen_message_id).map((member)=>member.seen_message_id.id);if(otherLastSeenMessageIds.length===0){return;}
return Math.min(...otherLastSeenMessageIds);},});lastSelfMessageSeenByEveryone=Record.one("Message",{compute(){if(!this.lastMessageSeenByAllId){return false;}
let res;for(let i=this.persistentMessages.length-1;i>=0;i--){const message=this.persistentMessages[i];if(!message.isSelfAuthored){continue;}
if(message.id>this.lastMessageSeenByAllId){continue;}
res=message;break;}
return res;},});get unknownMembersCount(){return this.memberCount-this.channelMembers.length;}
executeCommand(command,body=""){return this.store.env.services.orm.call("discuss.channel",command.methodName,[[this.id]],{body});}
async fetchChannelMembers(){if(this.fetchMembersState==="pending"){return;}
const previousState=this.fetchMembersState;this.fetchMembersState="pending";const known_member_ids=this.channelMembers.map((channelMember)=>channelMember.id);let data;try{data=await rpc("/discuss/channel/members",{channel_id:this.id,known_member_ids:known_member_ids,});}catch(e){this.fetchMembersState=previousState;throw e;}
this.fetchMembersState="fetched";this.store.insert(data);}
async fetchMessages({after,around,before}={}){this.status="loading";if(!["mail.box","discuss.channel"].includes(this.model)&&!this.id){this.isLoaded=true;return[];}
try{const{data,messages}=await this.fetchMessagesData({after,around,before});this.hasLoadingFailedError=undefined;this.store.insert(data,{html:true});this.hasLoadingFailed=false;return this.store.Message.insert(messages.reverse());}catch(e){this.hasLoadingFailed=true;this.hasLoadingFailedError=e;throw e;}finally{this.isLoaded=true;this.status="ready";}}
async fetchMessagesData({after,around,before}={}){return await rpc(this.getFetchRoute(),{...this.getFetchParams(),limit:!around&&around!==0?this.store.FETCH_LIMIT:this.store.FETCH_LIMIT*2,after,around,before,});}
async fetchMoreMessages(epoch="older"){if(this.status==="loading"||(epoch==="older"&&!this.loadOlder)||(epoch==="newer"&&!this.loadNewer)){return;}
const before=epoch==="older"?this.oldestPersistentMessage?.id:undefined;const after=epoch==="newer"?this.newestPersistentMessage?.id:undefined;try{const fetched=await this.fetchMessages({after,before});if((after!==undefined&&!this.messages.some((message)=>message.id===after))||(before!==undefined&&!this.messages.some((message)=>message.id===before))){return;}
const alreadyKnownMessages=new Set(this.messages.map(({id})=>id));const messagesToAdd=fetched.filter((message)=>!alreadyKnownMessages.has(message.id));if(epoch==="older"){this.messages.unshift(...messagesToAdd);}else{this.messages.push(...messagesToAdd);}
if(fetched.length<this.store.FETCH_LIMIT){if(epoch==="older"){this.loadOlder=false;}else if(epoch==="newer"){this.loadNewer=false;const missingMessages=this.pendingNewMessages.filter(({id})=>!alreadyKnownMessages.has(id));if(missingMessages.length>0){this.messages.push(...missingMessages);this.messages.sort((m1,m2)=>m1.id-m2.id);}}}
this._enrichMessagesWithTransient();}catch{}
this.pendingNewMessages=[];}
get effectiveSelf(){return this.store.self;}
get selves(){return[this.store.self];}
async fetchNewMessages(){if(this.status==="loading"||(this.isLoaded&&["discuss.channel","mail.box"].includes(this.model))){return;}
const after=this.isLoaded?this.newestPersistentMessage?.id:undefined;try{const fetched=await this.fetchMessages({after});let startIndex;if(after===undefined){startIndex=0;}else{const afterIndex=this.messages.findIndex((message)=>message.id===after);if(afterIndex===-1){return;}else{startIndex=afterIndex+1;}}
const alreadyKnownMessages=new Set(this.messages.map((m)=>m.id));const filtered=fetched.filter((message)=>!alreadyKnownMessages.has(message.id)&&(this.persistentMessages.length===0||message.id<this.oldestPersistentMessage.id||message.id>this.newestPersistentMessage.id));this.messages.splice(startIndex,0,...filtered);Object.assign(this,{loadOlder:after===undefined&&fetched.length===this.store.FETCH_LIMIT?true:after===undefined&&fetched.length!==this.store.FETCH_LIMIT?false:this.loadOlder,});}catch{}}
getFetchParams(){if(this.model==="discuss.channel"){return{channel_id:this.id};}
if(this.model==="mail.box"){return{};}
return{thread_id:this.id,thread_model:this.model,...this.rpcParams,};}
getFetchRoute(){if(this.model==="discuss.channel"){return"/discuss/channel/messages";}
if(this.model==="mail.box"&&this.id==="inbox"){return`/mail/inbox/messages`;}
if(this.model==="mail.box"&&this.id==="starred"){return`/mail/starred/messages`;}
if(this.model==="mail.box"&&this.id==="history"){return`/mail/history/messages`;}
return this.fetchRouteChatter;}
get fetchRouteChatter(){return"/mail/thread/messages";}
async leave(){await this.store.env.services.orm.call("discuss.channel","action_unfollow",[this.id]);}
async loadAround(messageId){if(this.status==="loading"||(this.isLoaded&&this.messages.some(({id})=>id===messageId))){return;}
try{this.isLoaded=false;this.scrollTop=undefined;this.messages=await this.fetchMessages({around:messageId});this.isLoaded=true;this.loadNewer=messageId!==undefined?true:false;this.loadOlder=true;const limit=!messageId&&messageId!==0?this.store.FETCH_LIMIT:this.store.FETCH_LIMIT*2;if(this.messages.length<limit){const olderMessagesCount=this.messages.filter(({id})=>id<messageId).length;const newerMessagesCount=this.messages.filter(({id})=>id>messageId).length;if(olderMessagesCount<limit/2-1){this.loadOlder=false;}
if(newerMessagesCount<limit/2){this.loadNewer=false;}}
this._enrichMessagesWithTransient();}catch{}}
async markAllMessagesAsRead(){await this.store.env.services.orm.silent.call("mail.message","mark_all_as_read",[[["model","=",this.model],["res_id","=",this.id],],]);this.message_needaction_counter=0;}
async markAsFetched(){await this.store.env.services.orm.silent.call("discuss.channel","channel_fetched",[[this.id],]);}
markAsRead({sync}={}){const newestPersistentMessage=this.newestPersistentOfAllMessage;if(!newestPersistentMessage&&!this.isLoaded){this.isLoadedDeferred.then(()=>new Promise(setTimeout)).then(()=>this.markAsRead());}
const alreadyReadBySelf=newestPersistentMessage?.isReadBySelf;if(this.selfMember){this.selfMember.syncUnread=sync??this.selfMember.syncUnread;this.selfMember.seen_message_id=newestPersistentMessage;}
if(newestPersistentMessage&&this.selfMember&&!alreadyReadBySelf){rpc("/discuss/channel/mark_as_read",{channel_id:this.id,last_message_id:newestPersistentMessage.id,sync,}).catch((e)=>{if(e.code!==404){throw e;}});}
if(this.message_needaction_counter>0){this.markAllMessagesAsRead();}}
async notifyAvatarToServer(data){await rpc("/discuss/channel/update_avatar",{channel_id:this.id,data,});}
async notifyDescriptionToServer(description){this.description=description;return this.store.env.services.orm.call("discuss.channel","channel_change_description",[[this.id]],{description});}
open(options){}
openChatWindow({fromMessagingMenu}={}){const cw=this.store.ChatWindow.insert(assignDefined({thread:this},{fromMessagingMenu}));this.store.chatHub.opened.delete(cw);this.store.chatHub.opened.unshift(cw);if(!isMobileOS()){cw.focus();}else{this.markAsRead();}
this.state="open";cw.notifyState();return cw;}
pin(){if(this.model!=="discuss.channel"||this.store.self.type!=="partner"){return;}
this.is_pinned=true;return this.store.env.services.orm.silent.call("discuss.channel","channel_pin",[this.id],{pinned:true});}
async rename(name){const newName=name.trim();if(newName!==this.displayName&&((newName&&this.channel_type==="channel")||this.isChatChannel)){if(this.channel_type==="channel"||this.channel_type==="group"){this.name=newName;await this.store.env.services.orm.call("discuss.channel","channel_rename",[[this.id]],{name:newName});}else if(this.supportsCustomChannelName){this.custom_channel_name=newName;await this.store.env.services.orm.call("discuss.channel","channel_set_custom_name",[[this.id]],{name:newName});}}}
addOrReplaceMessage(message,tmpMsg){if(tmpMsg&&tmpMsg.in(this.messages)&&this.effectiveSelf.eq(message.author)){this.messages.splice(this.messages.indexOf(tmpMsg),1,message);return;}
this.messages.add(message);}
async post(body,postData={},extraData={}){let tmpMsg;postData.attachments=postData.attachments?[...postData.attachments]:[];const{attachments,parentId,mentionedChannels,mentionedPartners}=postData;const params=await this.store.getMessagePostParams({body,postData,thread:this});Object.assign(params,extraData);const tmpId=this.store.getNextTemporaryId();params.context={...user.context,...params.context,temporary_id:tmpId};if(parentId){params.post_data.parent_id=parentId;}
if(this.model!=="discuss.channel"){params.thread_id=this.id;params.thread_model=this.model;}else{const tmpData={id:tmpId,attachments:attachments,res_id:this.id,model:"discuss.channel",};tmpData.author=this.effectiveSelf;if(parentId){tmpData.parentMessage=this.store.Message.get(parentId);}
const prettyContent=await prettifyMessageContent(body,this.store.getMentionsFromText(body,{mentionedChannels,mentionedPartners,}));tmpMsg=this.store.Message.insert({...tmpData,body:prettyContent,isPending:true,thread:this,},{html:true});this.messages.push(tmpMsg);if(this.selfMember){this.selfMember.syncUnread=true;this.selfMember.seen_message_id=tmpMsg;this.selfMember.new_message_separator=tmpMsg.id+1;}}
const data=await this.store.doMessagePost(params,tmpMsg);if(!data){return;}
const{Message:messages=[]}=this.store.insert(data,{html:true});const[message]=messages;this.addOrReplaceMessage(message,tmpMsg);if(this.selfMember?.seen_message_id?.id<message.id){this.selfMember.seen_message_id=message;this.selfMember.new_message_separator=message.id+1;}
tmpMsg?.delete();if(message.hasLink&&this.store.hasLinkPreviewFeature){rpc("/mail/link_preview",{message_id:message.id},{silent:true});}
return message;}
async setMainAttachmentFromIndex(index){this.mainAttachment=this.attachmentsInWebClientView[index];await this.store.env.services.orm.call("ir.attachment","register_as_main_attachment",[this.mainAttachment.id,]);}
_enrichMessagesWithTransient(){for(const message of this.transientMessages){if(message.id<this.oldestPersistentMessage&&!this.loadOlder){this.messages.unshift(message);}else if(message.id>this.newestPersistentMessage&&!this.loadNewer){this.messages.push(message);}else{let afterIndex=this.messages.findIndex((msg)=>msg.id>message.id);if(afterIndex===-1){afterIndex=this.messages.length+1;}
this.messages.splice(afterIndex-1,0,message);}}}}
Thread.register();return __exports;});;

/* /mail/static/src/core/common/volume_model.js */
odoo.define('@mail/core/common/volume_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const Volume=__exports.Volume=class Volume extends Record{static id="persona";persona=Record.one("Persona");volume=1;}
Volume.register();return __exports;});;

/* /mail/static/src/core/web_portal/message_patch.js */
odoo.define('@mail/core/web_portal/message_patch',['@web/core/utils/patch','@mail/core/common/message','@odoo/owl','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{patch}=require("@web/core/utils/patch");const{Message}=require("@mail/core/common/message");const{onWillUnmount}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");patch(Message.prototype,{setup(){super.setup(...arguments);this.state.lastReadMoreIndex=0;this.state.isReadMoreByIndex=new Map();onWillUnmount(()=>{this.messageBody.el?.querySelector(".o-mail-read-more-less")?.remove();});},prepareMessageBody(bodyEl){super.prepareMessageBody(...arguments);Array.from(bodyEl.querySelectorAll(".o-mail-read-more-less")).forEach((el)=>el.remove());this.insertReadMoreLess(bodyEl);},insertReadMoreLess(bodyEl){function prevAll(e,selector){const res=[];while((e=e.previousElementSibling)){if(e.matches(selector)){res.push(e);}}
return res;}
function prev(e,selector){while((e=e.previousElementSibling)){if(e.matches(selector)){return e;}}}
function hide(el){el.dataset.oMailDisplay=el.style.display;el.style.display="none";}
function toggle(el,condition=false){if(condition){let newDisplay=el.dataset.oMailDisplay;if(newDisplay==="none"){newDisplay=null;}
el.style.display=newDisplay;}else{hide(el);}}
const groups=[];let readMoreNodes;const ELEMENT_NODE=1;const TEXT_NODE=3;const childrenEl=Array.from(bodyEl.childNodes).filter(function(childEl){return(childEl.nodeType===ELEMENT_NODE||(childEl.nodeType===TEXT_NODE&&childEl.nodeValue.trim()));});for(const childEl of childrenEl){if(childEl.nodeType===TEXT_NODE&&prevAll(childEl,'[id*="stopSpelling"]').length>0){const newChildEl=document.createElement("span");newChildEl.textContent=childEl.textContent;newChildEl.dataset.oMailQuote="1";childEl.parentNode.replaceChild(newChildEl,childEl);}
if((childEl.nodeType===ELEMENT_NODE&&childEl.getAttribute("data-o-mail-quote"))||(childEl.nodeName==="BR"&&prev(childEl,'[data-o-mail-quote="1"]'))){if(!readMoreNodes){readMoreNodes=[];groups.push(readMoreNodes);}
hide(childEl);readMoreNodes.push(childEl);}else{readMoreNodes=undefined;this.insertReadMoreLess(childEl);}}
for(const group of groups){const index=this.state.lastReadMoreIndex++;const readMoreLessEl=document.createElement("a");readMoreLessEl.style.display="block";readMoreLessEl.className="o-mail-read-more-less";readMoreLessEl.href="#";readMoreLessEl.textContent=_t("Read More");group[0].parentNode.insertBefore(readMoreLessEl,group[0]);if(!this.state.isReadMoreByIndex.has(index)){this.state.isReadMoreByIndex.set(index,true);}
const updateFromState=()=>{const isReadMore=this.state.isReadMoreByIndex.get(index);for(const childEl of group){hide(childEl);toggle(childEl,!isReadMore);}
readMoreLessEl.textContent=isReadMore?_t("Read More").toString():_t("Read Less").toString();};readMoreLessEl.addEventListener("click",(e)=>{e.preventDefault();this.state.isReadMoreByIndex.set(index,!this.state.isReadMoreByIndex.get(index));updateFromState();});updateFromState();}},});return __exports;});;

/* /mail/static/src/utils/common/dates.js */
odoo.define('@mail/utils/common/dates',[],function(require){'use strict';let __exports={};const{DateTime}=luxon;__exports.computeDelay=computeDelay;function computeDelay(datetime){if(!datetime){return 0;}
const today=DateTime.now().startOf("day");return datetime.diff(today,"days").days;}
__exports.getMsToTomorrow=getMsToTomorrow;function getMsToTomorrow(){const now=new Date();const night=new Date(now.getFullYear(),now.getMonth(),now.getDate()+1,0,0,0);return night.getTime()-now.getTime();}
__exports.isToday=isToday;function isToday(datetime){if(!datetime){return false;}
return(datetime.toLocaleString(DateTime.DATE_FULL)===DateTime.now().toLocaleString(DateTime.DATE_FULL));}
return __exports;});;

/* /mail/static/src/utils/common/format.js */
odoo.define('@mail/utils/common/format',['@mail/utils/common/html','@odoo/owl','@web/core/browser/router','@web/core/emoji_picker/emoji_picker','@web/core/utils/html','@web/core/utils/strings','@web/core/utils/xml'],function(require){'use strict';let __exports={};const{createDocumentFragmentFromContent,htmlJoin,htmlReplace,htmlTrim,}=require("@mail/utils/common/html");const{markup}=require("@odoo/owl");const{stateToUrl}=require("@web/core/browser/router");const{loadEmoji}=require("@web/core/emoji_picker/emoji_picker");const{htmlEscape,setElementContent}=require("@web/core/utils/html");const{escapeRegExp,unaccent}=require("@web/core/utils/strings");const{setAttributes}=require("@web/core/utils/xml");const urlRegexp=/\b(?:https?:\/\/\d{1,3}(?:\.\d{1,3}){3}|(?:https?:\/\/|(?:www\.))[-a-z0-9@:%._+~#=\u00C0-\u024F\u1E00-\u1EFF]{1,256}\.[a-z]{2,13})\b(?:[-a-z0-9@:%_+~#?&[\]^|{}`\\'$//=\u00C0-\u024F\u1E00-\u1EFF]|[.]*[-a-z0-9@:%_+~#?&[\]^|{}`\\'$//=\u00C0-\u024F\u1E00-\u1EFF]|,(?!$| )|\.(?!$| |\.)|;(?!$| ))*/gi;__exports.prettifyMessageContent=prettifyMessageContent;async function prettifyMessageContent(rawBody,validRecords=[]){const escapedAndCompactContent=escapeAndCompactTextContent(rawBody);let body=htmlReplace(escapedAndCompactContent,/&nbsp;/g," ");body=htmlTrim(body);body=generateMentionsLinks(body,validRecords);body=await _generateEmojisOnHtml(body);body=parseAndTransform(body,addLink);return body;}
__exports.parseAndTransform=parseAndTransform;function parseAndTransform(htmlString,transformFunction){let children;try{const div=document.createElement("div");setElementContent(div,htmlString);children=Array.from(div.childNodes);}catch{const div=document.createElement("div");const pre=document.createElement("pre");setElementContent(pre,htmlString);div.appendChild(pre);children=Array.from(div.childNodes);}
return _parseAndTransform(children,transformFunction);}
function _parseAndTransform(nodes,transformFunction){if(!nodes){return;}
return htmlJoin(...Object.values(nodes).map((node)=>transformFunction(node,function(){return _parseAndTransform(node.childNodes,transformFunction);})));}
function linkify(text){let curIndex=0;let result="";let match;while((match=urlRegexp.exec(text))!==null){const url=match[0];const fixedUrl=!/^https?:\/\//i.test(url)?`http://${url}`:url;if(!URL.canParse(fixedUrl)){continue;}
result=htmlJoin(result,text.slice(curIndex,match.index));const{href}=URL.parse(fixedUrl);result=htmlJoin(result,markup`<a target="_blank" rel="noreferrer noopener" href="${href}">${url}</a>`);curIndex=match.index+match[0].length;}
return htmlJoin(result,text.slice(curIndex));}
__exports.addLink=addLink;function addLink(node,transformChildren){if(node.nodeType===3){const linkified=linkify(node.textContent);if(linkified.toString()!==node.textContent){const div=document.createElement("div");setElementContent(div,linkified);for(const childNode of[...div.childNodes]){node.parentNode.insertBefore(childNode,node);}
node.parentNode.removeChild(node);return linkified;}
return node.textContent;}
if(node.tagName==="A"){return markup(node.outerHTML);}
transformChildren();return markup(node.outerHTML);}
__exports.escapeAndCompactTextContent=escapeAndCompactTextContent;function escapeAndCompactTextContent(content){let value=htmlTrim(content);value=htmlReplace(value,/(\r|\n){2,}/g,markup("<br/><br/>"));value=htmlReplace(value,/(\r|\n)/g,markup("<br/>"));value=htmlReplace(value,/ /g,markup("&nbsp;"));value=htmlReplace(value,/([^>])&nbsp;([^<])/g,markup("$1 $2"));return value;}
function generateMentionsLinks(body,{partners=[],threads=[],specialMentions=[]}){const mentions=[];for(const partner of partners){const placeholder=`@-mention-partner-${partner.id}`;const text=`@${partner.name}`;mentions.push({class:"o_mail_redirect",id:partner.id,model:"res.partner",placeholder,text,});body=htmlReplace(body,text,placeholder);}
for(const thread of threads){const placeholder=`#-mention-channel-${thread.id}`;let className,text;if(thread.parent_channel_id){className="o_channel_redirect o_channel_redirect_asThread";text=`#${thread.parent_channel_id.displayName} > ${thread.displayName}`;}else{className="o_channel_redirect";text=`#${thread.displayName}`;}
mentions.push({class:className,id:thread.id,model:"discuss.channel",placeholder,text,});body=htmlReplace(body,text,placeholder);}
for(const special of specialMentions){body=htmlReplace(body,`@${special}`,markup(`<a href="#" class="o-discuss-mention">@${htmlEscape(special)}</a>`));}
for(const mention of mentions){const link=document.createElement("a");setAttributes(link,{href:stateToUrl({model:mention.model,resId:mention.id}),class:mention.class,"data-oe-id":mention.id,"data-oe-model":mention.model,target:"_blank",contenteditable:"false",});link.textContent=mention.text;body=htmlReplace(body,mention.placeholder,markup(link.outerHTML));}
return htmlEscape(body);}
async function _generateEmojisOnHtml(htmlString){const{emojis}=await loadEmoji();for(const emoji of emojis){for(const source of[...emoji.shortcodes,...emoji.emoticons]){const escapedSource=htmlJoin(String(source));const regexp=new RegExp("(\\s|^)("+escapeRegExp(escapedSource)+")(?=\\s|$)","g");htmlString=htmlReplace(htmlString,regexp,"$1"+emoji.codepoints);}}
return htmlEscape(htmlString);}
__exports.htmlToTextContentInline=htmlToTextContentInline;function htmlToTextContentInline(htmlString){htmlString=htmlReplace(htmlString,/<br\s*\/?>/gi," ");const div=document.createElement("div");try{setElementContent(div,htmlString);}catch{const pre=document.createElement("pre");setElementContent(pre,htmlString);div.appendChild(pre);}
return div.textContent.trim().replace(/[\n\r]/g,"").replace(/\s\s+/g," ");}
__exports.convertBrToLineBreak=convertBrToLineBreak;function convertBrToLineBreak(str){str=htmlReplace(str,/<br\s*\/?>/gi,"\n");return createDocumentFragmentFromContent(str).body.textContent;}
__exports.cleanTerm=cleanTerm;function cleanTerm(term){return unaccent((typeof term==="string"?term:"").toLowerCase());}
__exports.parseEmail=parseEmail;function parseEmail(text){if(!text){return;}
let result=text.match(/"?(.*?)"? <(.*@.*)>/);if(result){const name=(result[1]||"").trim().replace(/(^"|"$)/g,"");return[name,(result[2]||"").trim()];}
result=text.match(/(.*@.*)/);if(result){return[String(result[1]||"").trim(),String(result[1]||"").trim()];}
return[text,false];}
const EMOJI_REGEX=__exports.EMOJI_REGEX=/\p{Emoji_Presentation}|\p{Emoji}\uFE0F|\u200d/gu;return __exports;});;

/* /mail/static/src/utils/common/hooks.js */
odoo.define('@mail/utils/common/hooks',['@odoo/owl','@web/core/browser/browser','@web/core/l10n/translation','@web/core/utils/concurrency','@web/core/utils/draggable_hook_builder_owl','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{onMounted,onPatched,onWillUnmount,useComponent,useEffect,useRef,useState,}=require("@odoo/owl");const{browser}=require("@web/core/browser/browser");const{_t}=require("@web/core/l10n/translation");const{Deferred}=require("@web/core/utils/concurrency");const{makeDraggableHook}=require("@web/core/utils/draggable_hook_builder_owl");const{useService}=require("@web/core/utils/hooks");__exports.useLazyExternalListener=useLazyExternalListener;function useLazyExternalListener(target,eventName,handler,eventParams){const boundHandler=handler.bind(useComponent());let t;onMounted(()=>{t=target();if(!t){return;}
t.addEventListener(eventName,boundHandler,eventParams);});onPatched(()=>{const t2=target();if(t!==t2){if(t){t.removeEventListener(eventName,boundHandler,eventParams);}
if(t2){t2.addEventListener(eventName,boundHandler,eventParams);}
t=t2;}});onWillUnmount(()=>{if(!t){return;}
t.removeEventListener(eventName,boundHandler,eventParams);});}
__exports.onExternalClick=onExternalClick;function onExternalClick(refName,cb){let downTarget,upTarget;const ref=useRef(refName);function onClick(ev){if(ref.el&&!ref.el.contains(ev.composedPath()[0])){cb(ev,{downTarget,upTarget});upTarget=downTarget=null;}}
function onMousedown(ev){downTarget=ev.target;}
function onMouseup(ev){upTarget=ev.target;}
onMounted(()=>{document.body.addEventListener("mousedown",onMousedown,true);document.body.addEventListener("mouseup",onMouseup,true);document.body.addEventListener("click",onClick,true);});onWillUnmount(()=>{document.body.removeEventListener("mousedown",onMousedown,true);document.body.removeEventListener("mouseup",onMouseup,true);document.body.removeEventListener("click",onClick,true);});}
__exports.useHover=useHover;function useHover(refNames,{onHover,onAway,stateObserver,onHovering}={}){refNames=Array.isArray(refNames)?refNames:[refNames];const targets=[];let wasHovering=false;let hoveringTimeout;let awayTimeout;let lastHoveredTarget;for(const refName of refNames){targets.push({ref:refName.endsWith("*")?useRef(refName.substring(0,refName.length-1)):useRef(refName),});}
const state=useState({set isHover(newIsHover){if(this._isHover!==newIsHover){this._isHover=newIsHover;this._count++;}},get isHover(){void this._count;return this._isHover;},_count:0,_isHover:false,});function setHover(hovering){if(hovering&&!wasHovering){state.isHover=true;clearTimeout(awayTimeout);clearTimeout(hoveringTimeout);if(typeof onHover==="function"){onHover();}
if(Array.isArray(onHovering)){const[delay,cb]=onHovering;hoveringTimeout=setTimeout(()=>{cb();},delay);}}else if(!hovering){state.isHover=false;clearTimeout(awayTimeout);if(typeof onAway==="function"){awayTimeout=setTimeout(()=>{clearTimeout(hoveringTimeout);onAway();},200);}}
wasHovering=hovering;}
function onmouseenter(ev){if(state.isHover){return;}
for(const target of targets){if(!target.ref.el){continue;}
if(target.ref.el.contains(ev.target)){setHover(true);lastHoveredTarget=target;return;}}}
function onmouseleave(ev){if(!state.isHover){return;}
for(const target of targets){if(!target.ref.el){continue;}
if(target.ref.el.contains(ev.relatedTarget)){return;}}
setHover(false);lastHoveredTarget=null;}
for(const target of targets){useLazyExternalListener(()=>target.ref.el,"mouseenter",(ev)=>onmouseenter(ev),true);useLazyExternalListener(()=>target.ref.el,"mouseleave",(ev)=>onmouseleave(ev),true);}
if(stateObserver){useEffect(()=>{if(lastHoveredTarget&&!lastHoveredTarget.ref.el){setHover(false);lastHoveredTarget=null;}},stateObserver);}
return state;}
__exports.useOnBottomScrolled=useOnBottomScrolled;function useOnBottomScrolled(refName,callback,threshold=1){const ref=useRef(refName);function onScroll(){if(Math.abs(ref.el.scrollTop+ref.el.clientHeight-ref.el.scrollHeight)<threshold){callback();}}
onMounted(()=>{ref.el.addEventListener("scroll",onScroll);});onWillUnmount(()=>{ref.el.removeEventListener("scroll",onScroll);});}
__exports.useVisible=useVisible;function useVisible(refName,cb,{ready=true}={}){const ref=useRef(refName);const state=useState({isVisible:undefined,ready,});function setValue(value){state.isVisible=value;cb(state.isVisible);}
const observer=new IntersectionObserver((entries)=>{setValue(entries.at(-1).isIntersecting);});useEffect((el,ready)=>{if(el&&ready){observer.observe(el);return()=>{setValue(undefined);observer.unobserve(el);};}},()=>[ref.el,state.ready]);return state;}
__exports.useMessageHighlight=useMessageHighlight;function useMessageHighlight(duration=2000){let timeout;const notification=useState(useService("notification"));const state=useState({clearHighlight(){if(this.highlightedMessageId){browser.clearTimeout(timeout);timeout=null;this.highlightedMessageId=null;}},async highlightMessage(message,thread){if(thread.model!=="mail.box"&&thread.notEq(message.thread)){return;}
await thread.loadAround(message.id);if(message.isEmpty){notification.add(_t("The message has been deleted."));return;}
const lastHighlightedMessageId=state.highlightedMessageId;this.clearHighlight();if(lastHighlightedMessageId===message.id){await new Promise(setTimeout);}
thread.scrollTop=undefined;state.highlightedMessageId=message.id;timeout=browser.setTimeout(()=>this.clearHighlight(),duration);},scrollPromise:null,scrollTo(el){state.scrollPromise?.resolve();const scrollPromise=new Deferred();state.scrollPromise=scrollPromise;if("onscrollend"in window){document.addEventListener("scrollend",scrollPromise.resolve,{capture:true,once:true,});}else{setTimeout(scrollPromise.resolve,250);}
el.scrollIntoView({behavior:"smooth",block:"center"});return scrollPromise;},highlightedMessageId:null,});return state;}
__exports.useSelection=useSelection;function useSelection({refName,model,preserveOnClickAwayPredicate=()=>false}){const ui=useState(useService("ui"));const ref=useRef(refName);function onSelectionChange(){const activeElement=ref.el?.getRootNode().activeElement;if(activeElement&&activeElement===ref.el){Object.assign(model,{start:ref.el.selectionStart,end:ref.el.selectionEnd,direction:ref.el.selectionDirection,});}}
onExternalClick(refName,async(ev)=>{if(await preserveOnClickAwayPredicate(ev)){return;}
if(!ref.el){return;}
Object.assign(model,{start:ref.el.value.length,end:ref.el.value.length,direction:ref.el.selectionDirection,});});onMounted(()=>{document.addEventListener("selectionchange",onSelectionChange);document.addEventListener("input",onSelectionChange);});onWillUnmount(()=>{document.removeEventListener("selectionchange",onSelectionChange);document.removeEventListener("input",onSelectionChange);});return{restore(){ref.el?.setSelectionRange(model.start,model.end,model.direction);},moveCursor(position){model.start=model.end=position;if(!ui.isSmall){ref.el.selectionStart=ref.el.selectionEnd=position;}},};}
__exports.useMessageEdition=useMessageEdition;function useMessageEdition(){const state=useState({composerOfThread:null,editingMessage:null,exitEditMode(){state.editingMessage=null;if(state.composerOfThread){state.composerOfThread.props.composer.autofocus++;}},});return state;}
__exports.useMessageToReplyTo=useMessageToReplyTo;function useMessageToReplyTo(){return useState({cancel(){Object.assign(this,{message:null,thread:null});},isNotSelected(thread,message){return thread.eq(this.thread)&&message.notEq(this.message);},isSelected(thread,message){return thread.eq(this.thread)&&message.eq(this.message);},message:null,thread:null,toggle(thread,message){if(message.eq(this.message)){this.cancel();}else{Object.assign(this,{message,thread});}},});}
__exports.useSequential=useSequential;function useSequential(){let inProgress=false;let nextFunction;let nextResolve;let nextReject;async function call(){const resolve=nextResolve;const reject=nextReject;const func=nextFunction;nextResolve=undefined;nextReject=undefined;nextFunction=undefined;inProgress=true;try{const data=await func();resolve(data);}catch(e){reject(e);}
inProgress=false;if(nextFunction&&nextResolve){call();}}
return(func)=>{nextResolve?.();const prom=new Promise((resolve,reject)=>{nextResolve=resolve;nextReject=reject;});nextFunction=func;if(!inProgress){call();}
return prom;};}
__exports.useDiscussSystray=useDiscussSystray;function useDiscussSystray(){const ui=useState(useService("ui"));return{class:"o-mail-DiscussSystray-class",get contentClass(){return`d-flex flex-column flex-grow-1 ${
                ui.isSmall ? "overflow-auto w-100 mh-100" : ""
            }`;},get menuClass(){return`p-0 o-mail-DiscussSystray ${
                ui.isSmall
                    ? "o-mail-systrayFullscreenDropdownMenu start-0 w-100 mh-100 d-flex flex-column mt-0 border-0 shadow-lg"
                    : ""
            }`;},};}
const useMovable=__exports.useMovable=makeDraggableHook({name:"useMovable",onWillStartDrag({ctx,addCleanup,addStyle,getRect}){const{height}=getRect(ctx.current.element);ctx.current.container=document.createElement("div");addStyle(ctx.current.container,{position:"fixed",top:0,bottom:`${height}px`,left:0,right:0,});ctx.current.element.after(ctx.current.container);addCleanup(()=>ctx.current.container.remove());},onDrop({ctx,getRect}){const{top,left}=getRect(ctx.current.element);return{top,left};},});return __exports;});;

/* /mail/static/src/utils/common/html.js */
odoo.define('@mail/utils/common/html',['@odoo/owl','@web/core/utils/html'],function(require){'use strict';let __exports={};const{markup}=require("@odoo/owl");const{htmlEscape,setElementContent}=require("@web/core/utils/html");__exports.createDocumentFragmentFromContent=createDocumentFragmentFromContent;function createDocumentFragmentFromContent(content){const div=document.createElement("div");setElementContent(div,content);return new DOMParser().parseFromString(div.innerHTML,"text/html");}
__exports.htmlJoin=htmlJoin;function htmlJoin(...args){return markup(args.map((arg)=>htmlEscape(arg)).join(""));}
__exports.htmlReplace=htmlReplace;function htmlReplace(content,search,replacement){content=htmlEscape(content);if(typeof search==="string"||search instanceof String){search=htmlEscape(search);}
replacement=htmlEscape(replacement);return markup(content.replace(search,replacement));}
__exports.htmlTrim=htmlTrim;function htmlTrim(content){content=htmlEscape(content);return markup(content.trim());}
return __exports;});;

/* /mail/static/src/utils/common/misc.js */
odoo.define('@mail/utils/common/misc',['@odoo/owl','@web/core/network/rpc'],function(require){'use strict';let __exports={};const{reactive}=require("@odoo/owl");const{rpc}=require("@web/core/network/rpc");__exports.assignDefined=assignDefined;function assignDefined(obj,data,keys=Object.keys(data)){for(const key of keys){if(data[key]!==undefined){obj[key]=data[key];}}
return obj;}
__exports.assignIn=assignIn;function assignIn(obj,data,keys=Object.keys(data)){for(const key of keys){if(key in data){obj[key]=data[key];}}
return obj;}
__exports.nearestGreaterThanOrEqual=nearestGreaterThanOrEqual;function nearestGreaterThanOrEqual(list,target,itemToCompareVal){const findNext=(left,right,next)=>{if(left>right){return next;}
const index=Math.floor((left+right)/2);const item=list[index];const val=itemToCompareVal?.(item)??item;if(val===target){return item;}else if(val>target){return findNext(left,index-1,item);}else{return findNext(index+1,right,next);}};return findNext(0,list.length-1,null);}
const mailGlobal=__exports.mailGlobal={isInTest:false,};__exports.rpcWithEnv=rpcWithEnv;function rpcWithEnv(){return rpc;}
__exports.isDragSourceExternalFile=isDragSourceExternalFile;function isDragSourceExternalFile(dataTransfer){const dragDataType=dataTransfer.types;if(dragDataType.constructor===window.DOMStringList){return dragDataType.contains("Files");}
if(dragDataType.constructor===Array){return dragDataType.includes("Files");}
return false;}
__exports.onChange=onChange;function onChange(target,key,callback){let proxy;function _observe(){const val=proxy[key];if(typeof val==="object"&&val!==null){void Object.keys(val);}
if(Array.isArray(val)){void val.length;void val.forEach((i)=>i);}}
if(Array.isArray(key)){for(const k of key){onChange(target,k,callback);}
return;}
proxy=reactive(target,()=>{_observe();callback();});_observe();return proxy;}
__exports.closeStream=closeStream;function closeStream(stream){stream?.getTracks?.().forEach((track)=>track.stop());}
__exports.compareDatetime=compareDatetime;function compareDatetime(date1,date2){if(date1?.ts===date2?.ts){return 0;}
if(!date1){return-1;}
if(!date2){return 1;}
return date1.ts-date2.ts;}
function compareVersion(v1,v2){const parts1=v1.split(".");const parts2=v2.split(".");for(let i=0;i<Math.max(parts1.length,parts2.length);i++){const num1=parseInt(parts1[i])||0;const num2=parseInt(parts2[i])||0;if(num1<num2){return-1;}
if(num1>num2){return 1;}}
return 0;}
__exports.parseVersion=parseVersion;function parseVersion(v){return{isLowerThan(other){return compareVersion(v,other)<0;},};}
return __exports;});;

/* /mail/static/src/chatter/web_portal/chatter.js */
odoo.define('@mail/chatter/web_portal/chatter',['@mail/core/common/composer','@mail/core/common/thread','@odoo/owl','@web/core/l10n/translation','@web/core/utils/hooks','@web/core/utils/timing'],function(require){'use strict';let __exports={};const{Composer}=require("@mail/core/common/composer");const{Thread}=require("@mail/core/common/thread");const{Component,onMounted,onWillUpdateProps,useChildSubEnv,useRef,useState,}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const{useService}=require("@web/core/utils/hooks");const{useThrottleForAnimation}=require("@web/core/utils/timing");const Chatter=__exports.Chatter=class Chatter extends Component{static template="mail.Chatter";static components={Thread,Composer};static props=["composer?","threadId?","threadModel","twoColumns?"];static defaultProps={composer:true,threadId:false,twoColumns:false};setup(){this.store=useState(useService("mail.store"));this.state=useState({jumpThreadPresent:0,thread:undefined,aside:false,});this.rootRef=useRef("root");this.onScrollDebounced=useThrottleForAnimation(this.onScroll);useChildSubEnv(this.childSubEnv);onMounted(this._onMounted);onWillUpdateProps((nextProps)=>{if(this.props.threadId!==nextProps.threadId||this.props.threadModel!==nextProps.threadModel){this.changeThread(nextProps.threadModel,nextProps.threadId);}
if(!this.env.chatter||this.env.chatter?.fetchData){if(this.env.chatter){this.env.chatter.fetchData=false;}
this.load(this.state.thread,this.requestList);}});}
get afterPostRequestList(){return["messages"];}
get childSubEnv(){return{inChatter:this.state};}
get onCloseFullComposerRequestList(){return["messages"];}
get requestList(){return[];}
changeThread(threadModel,threadId){this.state.thread=this.store.Thread.insert({model:threadModel,id:threadId});if(threadId===false){if(this.state.thread.messages.length===0){this.state.thread.messages.push({id:this.store.getNextTemporaryId(),author:this.state.thread.effectiveSelf,body:_t("Creating a new record..."),message_type:"notification",thread:this.state.thread,trackingValues:[],res_id:threadId,model:threadModel,});}}}
load(thread,requestList){if(!thread.id||!this.state.thread?.eq(thread)){return;}
thread.fetchData(requestList);}
onCloseFullComposerCallback(){this.load(this.state.thread,this.onCloseFullComposerRequestList);}
_onMounted(){this.changeThread(this.props.threadModel,this.props.threadId);if(!this.env.chatter||this.env.chatter?.fetchData){if(this.env.chatter){this.env.chatter.fetchData=false;}
this.load(this.state.thread,this.requestList);}}
onPostCallback(){this.state.jumpThreadPresent++;this.load(this.state.thread,this.afterPostRequestList);}
onScroll(){this.state.isTopStickyPinned=this.rootRef.el.scrollTop!==0;}}
return __exports;});;

/* /mail/static/src/chatter/web_portal/composer_patch.js */
odoo.define('@mail/chatter/web_portal/composer_patch',['@mail/core/common/composer','@web/core/l10n/translation','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Composer}=require("@mail/core/common/composer");const{_t}=require("@web/core/l10n/translation");const{patch}=require("@web/core/utils/patch");patch(Composer.prototype,{get placeholder(){if(this.thread&&this.thread.model!=="discuss.channel"&&!this.props.placeholder){if(this.props.type==="message"){return _t("Send a message to followers…");}else{return _t("Log an internal note…");}}
return super.placeholder;},});return __exports;});;

/* /mail/static/src/chatter/web_portal/thread_model_patch.js */
odoo.define('@mail/chatter/web_portal/thread_model_patch',['@mail/core/common/thread_model','@web/core/network/rpc','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Thread}=require("@mail/core/common/thread_model");const{rpc}=require("@web/core/network/rpc");const{patch}=require("@web/core/utils/patch");patch(Thread.prototype,{async fetchData(requestList){if(requestList.includes("messages")){this.fetchNewMessages();}
const result=await rpc("/mail/thread/data",{request_list:requestList,thread_id:this.id,thread_model:this.model,...this.rpcParams,});this.store.insert(result,{html:true});},});return __exports;});;

/* /mail/static/src/discuss/typing/common/typing.js */
odoo.define('@mail/discuss/typing/common/typing',['@odoo/owl','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{Component}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const Typing=__exports.Typing=class Typing extends Component{static defaultProps={size:"small",displayText:true,};static props=["channel?","size?","displayText?","member?"];static template="discuss.Typing";get text(){const typingMemberNames=this.props.member?[this.props.member.name]:this.props.channel.otherTypingMembers.map(({name})=>name);if(typingMemberNames.length===1){return _t("%s is typing...",typingMemberNames[0]);}
if(typingMemberNames.length===2){return _t("%(user1)s and %(user2)s are typing...",{user1:typingMemberNames[0],user2:typingMemberNames[1],});}
return _t("%(user1)s, %(user2)s and more are typing...",{user1:typingMemberNames[0],user2:typingMemberNames[1],});}}
return __exports;});;

/* /mail/static/src/discuss/core/common/action_panel.js */
odoo.define('@mail/discuss/core/common/action_panel',['@odoo/owl','@web/core/resizable_panel/resizable_panel','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{Component,useState}=require("@odoo/owl");const{ResizablePanel}=require("@web/core/resizable_panel/resizable_panel");const{useService}=require("@web/core/utils/hooks");const ActionPanel=__exports.ActionPanel=class ActionPanel extends Component{static template="mail.ActionPanel";static components={ResizablePanel};static props=["icon?","title?","resizable?","slots?","initialWidth?","minWidth?"];static defaultProps={resizable:true};setup(){super.setup();this.store=useState(useService("mail.store"));}
get classNames(){return`o-mail-ActionPanel overflow-auto d-flex flex-column flex-shrink-0 position-relative py-2 pt-0 h-100 bg-inherit ${
            !this.env.inChatter ? " px-2" : " o-mail-ActionPanel-chatter"
        } ${this.env.inDiscussApp ? " o-mail-discussSidebarBgColor" : ""}`;}}
return __exports;});;

/* /portal/static/src/chatter/core/chatter_patch.js */
odoo.define('@portal/chatter/core/chatter_patch',['@mail/chatter/web_portal/chatter'],function(require){'use strict';let __exports={};const{Chatter}=require("@mail/chatter/web_portal/chatter");Chatter.template="portal.Chatter";return __exports;});;

/* /portal/static/src/chatter/core/composer_patch.js */
odoo.define('@portal/chatter/core/composer_patch',['@mail/core/common/composer','@web/core/l10n/translation','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Composer}=require("@mail/core/common/composer");const{_t}=require("@web/core/l10n/translation");const{patch}=require("@web/core/utils/patch");patch(Composer.prototype,{get placeholder(){if(this.env.inFrontendPortalChatter){return _t("Write a message…");}
return super.placeholder;},});return __exports;});;

/* /portal/static/src/chatter/core/picker_patch.js */
odoo.define('@portal/chatter/core/picker_patch',['@mail/core/common/picker','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Picker}=require("@mail/core/common/picker");const{patch}=require("@web/core/utils/patch");patch(Picker.prototype,{get popoverSettings(){const settings=super.popoverSettings;settings.fixedPosition=false;return settings;},});return __exports;});;

/* /portal/static/src/chatter/core/thread_model_patch.js */
odoo.define('@portal/chatter/core/thread_model_patch',['@mail/core/common/thread_model','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Thread}=require("@mail/core/common/thread_model");const{patch}=require("@web/core/utils/patch");patch(Thread.prototype,{get fetchRouteChatter(){return"/mail/chatter_fetch";},});return __exports;});;

/* /portal/static/src/chatter/frontend/attachment_upload_service_patch.js */
odoo.define('@portal/chatter/frontend/attachment_upload_service_patch',['@mail/core/common/attachment_upload_service','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{AttachmentUploadService}=require("@mail/core/common/attachment_upload_service");const{patch}=require("@web/core/utils/patch");patch(AttachmentUploadService.prototype,{_buildFormData(formData,file,thread,composer,tmpId,options){super._buildFormData(...arguments);if(thread.rpcParams.hash&&thread.rpcParams.pid){formData.append("hash",thread.rpcParams.hash);formData.append("pid",thread.rpcParams.pid);}
if(thread.rpcParams.token){formData.append("token",thread.rpcParams.token);}
return formData;},});return __exports;});;

/* /portal/static/src/chatter/frontend/chatter_patch.js */
odoo.define('@portal/chatter/frontend/chatter_patch',['@mail/chatter/web_portal/chatter','@web/core/utils/patch','@odoo/owl'],function(require){'use strict';let __exports={};const{Chatter}=require("@mail/chatter/web_portal/chatter");const{patch}=require("@web/core/utils/patch");const{useRef,onWillPatch,useEffect}=require("@odoo/owl");patch(Chatter.prototype,{setup(){super.setup(...arguments);this.topRef=useRef("top");onWillPatch(()=>{const headerEl=document.querySelector("#wrapwrap header");if(!this.props.twoColumns&&headerEl&&!headerEl.matches(".o_header_sidebar")){const paddingTop=headerEl.getBoundingClientRect().height+15+"px";this.observer=new window.IntersectionObserver(([e])=>(e.target.style.paddingTop=e.target.getBoundingClientRect().y<1?paddingTop:"20px"),{threshold:[1],});}});useEffect(()=>{if(this.topRef.el){this.observer?.observe(this.topRef.el);}},()=>[this.topRef.el]);},});return __exports;});;

/* /portal/static/src/chatter/frontend/composer_model_patch.js */
odoo.define('@portal/chatter/frontend/composer_model_patch',['@mail/core/common/composer_model','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Composer}=require("@mail/core/common/composer_model");const{patch}=require("@web/core/utils/patch");patch(Composer.prototype,{portalComment:false,});return __exports;});;

/* /portal/static/src/chatter/frontend/composer_patch.js */
odoo.define('@portal/chatter/frontend/composer_patch',['@mail/core/common/composer','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Composer}=require("@mail/core/common/composer");const{patch}=require("@web/core/utils/patch");patch(Composer.prototype,{setup(){super.setup();if(this.env.inFrontendPortalChatter){this.suggestion=undefined;}},get showComposerAvatar(){return super.showComposerAvatar||(this.compact&&this.props.composer.portalComment);},get shouldHideFromMessageListOnDelete(){return this.env.inFrontendPortalChatter||super.shouldHideFromMessageListOnDelete;},});return __exports;});;

/* /portal/static/src/chatter/frontend/message_model_patch.js */
odoo.define('@portal/chatter/frontend/message_model_patch',['@web/core/utils/patch','@mail/core/common/message_model'],function(require){'use strict';let __exports={};const{patch}=require("@web/core/utils/patch");const{Message}=require("@mail/core/common/message_model");patch(Message.prototype,{get canToggleStar(){let result=super.canToggleStar;if(this.thread&&this.thread.model!=="discuss.channel"){result=result&&this.thread.hasReadAccess;}
return result;},});return __exports;});;

/* /portal/static/src/chatter/frontend/message_patch.js */
odoo.define('@portal/chatter/frontend/message_patch',['@mail/core/common/message','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Message}=require("@mail/core/common/message");const{patch}=require("@web/core/utils/patch");patch(Message.prototype,{get authorAvatarUrl(){if(this.message.author_avatar_url){return this.message.author_avatar_url;}
if(this.message.thread.access_token){return`/mail/avatar/mail.message/${this.message.id}/author_avatar/50x50?access_token=${this.message.thread.access_token}`;}
return super.authorAvatarUrl;},get shouldHideFromMessageListOnDelete(){return this.env.inFrontendPortalChatter||super.shouldHideFromMessageListOnDelete;},});return __exports;});;

/* /portal/static/src/chatter/frontend/portal_chatter.js */
odoo.define('@portal/chatter/frontend/portal_chatter',['@mail/chatter/web_portal/chatter','@web/core/overlay/overlay_container','@odoo/owl','@web/core/utils/hooks'],function(require){'use strict';let __exports={};const{Chatter}=require("@mail/chatter/web_portal/chatter");const{OverlayContainer}=require("@web/core/overlay/overlay_container");const{Component,xml,useSubEnv}=require("@odoo/owl");const{useService}=require("@web/core/utils/hooks");const PortalChatter=__exports.PortalChatter=class PortalChatter extends Component{static template=xml`
        <Chatter threadId="props.resId" threadModel="props.resModel" composer="props.composer" twoColumns="props.twoColumns"/>
        <div class="position-fixed" style="z-index:1030"><OverlayContainer overlays="overlayService.overlays"/></div>
    `;static components={Chatter,OverlayContainer};static props=["resId","resModel","composer","twoColumns","displayRating"];setup(){useSubEnv({displayRating:this.props.displayRating,inFrontendPortalChatter:true,});this.overlayService=useService("overlay");this.store=useService("mail.store");this.env.bus.addEventListener("reload_chatter_content",(ev)=>this._reloadChatterContent(ev.detail));}
async _reloadChatterContent(data){const thread=this.store.Thread.get({id:this.props.resId,model:this.props.resModel,});thread.messages=await thread.fetchMessages();}}
return __exports;});;

/* /portal/static/src/chatter/frontend/portal_chatter_service.js */
odoo.define('@portal/chatter/frontend/portal_chatter_service',['@portal/chatter/frontend/portal_chatter','@odoo/owl','@web/core/assets','@web/core/registry','@web/core/network/rpc','@web/session','@web/core/l10n/translation','@web/core/templates'],function(require){'use strict';let __exports={};const{PortalChatter}=require("@portal/chatter/frontend/portal_chatter");const{App}=require("@odoo/owl");const{AssetsLoadingError,getBundle}=require("@web/core/assets");const{registry}=require("@web/core/registry");const{rpc}=require("@web/core/network/rpc");const{session}=require("@web/session");const{_t}=require("@web/core/l10n/translation");const{getTemplate}=require("@web/core/templates");const PortalChatterService=__exports.PortalChatterService=class PortalChatterService{constructor(env,services){this.setup(env,services);}
setup(env,services){this.store=services["mail.store"];this.busService=services.bus_service;}
async createShadow(root){const shadow=root.attachShadow({mode:"open"});try{const res=await getBundle("portal.assets_chatter_style");for(const url of res.cssLibs){const link=document.createElement("link");link.rel="stylesheet";link.href=url;shadow.appendChild(link);await new Promise((res,rej)=>{link.addEventListener("load",res);link.addEventListener("error",rej);});}}catch(e){if(e instanceof AssetsLoadingError&&e.cause instanceof TypeError){return new Promise(()=>{});}else{throw e;}}
return shadow;}
async initialize(env){const chatterEl=document.querySelector(".o_portal_chatter");const props={resId:parseInt(chatterEl.getAttribute("data-res_id")),resModel:chatterEl.getAttribute("data-res_model"),composer:parseInt(chatterEl.getAttribute("data-allow_composer"))&&(chatterEl.getAttribute("data-token")||!session.is_public),twoColumns:chatterEl.getAttribute("data-two_columns")==="true"?true:false,displayRating:chatterEl.getAttribute("data-display_rating")==="True"?true:false,};const root=document.createElement("div");root.setAttribute("id","chatterRoot");if(props.twoColumns){root.classList.add("p-0");}
chatterEl.appendChild(root);this.createShadow(root).then((shadow)=>{new App(PortalChatter,{env,getTemplate,props,translatableAttributes:["data-tooltip"],translateFn:_t,dev:env.debug,}).mount(shadow);});const thread=this.store.Thread.insert({model:props.resModel,id:props.resId});Object.assign(thread,{access_token:chatterEl.getAttribute("data-token"),hash:chatterEl.getAttribute("data-hash"),pid:parseInt(chatterEl.getAttribute("data-pid")),});const data=await rpc("/portal/chatter_init",{thread_model:props.resModel,thread_id:props.resId,...thread.rpcParams,},{silent:true});this.store.insert(data);odoo.portalChatterReady.resolve(true);}}
const portalChatterService=__exports.portalChatterService={dependencies:["mail.store","bus_service"],start(env,services){const portalChatter=new PortalChatterService(env,services);portalChatter.initialize(env);return portalChatter;},};registry.category("services").add("portal.chatter",portalChatterService);return __exports;});;

/* /portal/static/src/chatter/frontend/store_service_patch.js */
odoo.define('@portal/chatter/frontend/store_service_patch',['@mail/core/common/store_service','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Store}=require("@mail/core/common/store_service");const{patch}=require("@web/core/utils/patch");patch(Store.prototype,{async initialize(){if(!this.initMessagingParams.init_messaging.channel_types){this.isReady.resolve();return;}
return super.initialize();},});return __exports;});;

/* /portal/static/src/chatter/frontend/thread_model_patch.js */
odoo.define('@portal/chatter/frontend/thread_model_patch',['@mail/core/common/thread_model','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Thread}=require("@mail/core/common/thread_model");const{patch}=require("@web/core/utils/patch");patch(Thread.prototype,{setup(){super.setup(...arguments);this.hasReadAccess;},get effectiveSelf(){if(this.portal_partner&&this.store.self.type!=="partner"){return this.portal_partner;}
return super.effectiveSelf;},get selves(){const result=super.selves;if(this.portal_partner){result.push(this.portal_partner);}
return result;},get rpcParams(){return{...super.rpcParams,...(this.access_token?{token:this.access_token}:{}),...(this.hash?{hash:this.hash}:{}),...(this.pid?{pid:this.pid}:{}),};},});return __exports;});;

/* /rating/static/src/core/common/message_model_patch.js */
odoo.define('@rating/core/common/message_model_patch',['@mail/core/common/message_model','@mail/core/common/record','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Message}=require("@mail/core/common/message_model");const{Record}=require("@mail/core/common/record");const{patch}=require("@web/core/utils/patch");patch(Message.prototype,{setup(){super.setup(...arguments);this.rating_id=Record.one("rating.rating");},computeIsEmpty(){return super.computeIsEmpty()&&!this.rating_id&&!this.rating_value;},async remove(){const data=await super.remove(...arguments);this.rating_value=false;return data;},get removeParams(){return{...super.removeParams,rating_value:false};},});return __exports;});;

/* /rating/static/src/core/common/rating_model.js */
odoo.define('@rating/core/common/rating_model',['@mail/core/common/record'],function(require){'use strict';let __exports={};const{Record}=require("@mail/core/common/record");const Rating=__exports.Rating=class Rating extends Record{static _name="rating.rating";static id="id";id;rating;rating_image_url;rating_text;}
Rating.register();return __exports;});;

/* /portal_rating/static/src/chatter/frontend/chatter_patch.js */
odoo.define('@portal_rating/chatter/frontend/chatter_patch',['@mail/chatter/web_portal/chatter','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Chatter}=require("@mail/chatter/web_portal/chatter");const{patch}=require("@web/core/utils/patch");const chatterPatch={async onClickStarDomain(star){const{thread}=this.state;Object.assign(thread,{loadOlder:false,rating_stats:this.ratingStats,selectedRating:star,});thread.messages=await thread.fetchMessages();thread.loadOlder=thread.messages.length===this.store.FETCH_LIMIT;},async onClickStarDomainReset(){const{thread}=this.state;Object.assign(thread,{loadOlder:false,selectedRating:false,});thread.messages=await thread.fetchMessages();thread.loadOlder=thread.messages.length===this.store.FETCH_LIMIT;},get ratingStats(){return this.state.thread?.messages.at(-1)?.rating_stats||this.state.thread?.rating_stats;},};patch(Chatter.prototype,chatterPatch);return __exports;});;

/* /portal_rating/static/src/chatter/frontend/composer_patch.js */
odoo.define('@portal_rating/chatter/frontend/composer_patch',['@mail/core/common/composer','@web/core/utils/patch','@web/core/network/rpc','@odoo/owl','@web/core/browser/feature_detection'],function(require){'use strict';let __exports={};const{Composer}=require("@mail/core/common/composer");const{patch}=require("@web/core/utils/patch");const{rpc}=require("@web/core/network/rpc");const{useState}=require("@odoo/owl");const{isMobileOS}=require("@web/core/browser/feature_detection");const MAX_STAR_RATING=5;const DEFAULT_STAR_RATING=4;patch(Composer.prototype,{setup(){super.setup(...arguments);this.MAX_STAR_RATING=MAX_STAR_RATING;this.portalState=useState({hoveredRatingValue:undefined,ratingValue:DEFAULT_STAR_RATING,get starValue(){return this.hoveredRatingValue;},set starValue(val){this.hoveredRatingValue=val;},});},get allowUpload(){return super.allowUpload&&!this.props.composer.portalComment;},async editMessage(){if(this.props.composer.portalComment){await this.savePublisherComment();return;}
await super.editMessage();},async savePublisherComment(){if(!this.state.active){return;}
this.state.active=false;const data=await rpc("/website/rating/comment",{rating_id:this.message.rating.id,publisher_comment:this.props.composer.text.trim(),});this.message.rating=data;this.props.onPostCallback();},get canProcessMessage(){return super.canProcessMessage||(this.message&&this.message.rating_value);},get askDeleteFromEdit(){return super.askDeleteFromEdit&&!this.message.rating_value;},get visibleRatingValue(){return this.portalState.hoveredRatingValue??this.portalState.ratingValue;},onMoveStar(ev){this.handleStar(ev,{hovered:!isMobileOS()});},handleStar(ev,{hovered}={}){const index=parseInt(ev.currentTarget.getAttribute("index"));if(Number.isNaN(index)||index<0||index>MAX_STAR_RATING-1){if(hovered){this.portalState.hoveredRatingValue=undefined;}
return;}
if(hovered){this.portalState.hoveredRatingValue=index+1;}else{this.portalState.ratingValue=index+1;}},onClickStar(ev){this.handleStar(ev);},onMouseLeaveStar(ev){if(!isMobileOS()){this.handleStar(ev,{hovered:true});}},get postData(){const postData=super.postData;if(this.env.displayRating&&!this.message){postData.rating_value=this.portalState.ratingValue;}
this.portalState.ratingValue=DEFAULT_STAR_RATING;return postData;},});return __exports;});;

/* /portal_rating/static/src/chatter/frontend/message_model_patch.js */
odoo.define('@portal_rating/chatter/frontend/message_model_patch',['@mail/core/common/message_model','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Message}=require("@mail/core/common/message_model");const{patch}=require("@web/core/utils/patch");const messagePatch={async remove({removeFromThread=false}={}){const data=await super.remove(...arguments);if(this.thread&&removeFromThread){this.thread.messages.forEach((message)=>{message.rating_stats=this.thread.rating_stats;});}
return data;},};patch(Message.prototype,messagePatch);return __exports;});;

/* /portal_rating/static/src/chatter/frontend/message_patch.js */
odoo.define('@portal_rating/chatter/frontend/message_patch',['@mail/core/common/message','@mail/utils/common/format','@web/core/network/rpc','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Message}=require("@mail/core/common/message");const{convertBrToLineBreak}=require("@mail/utils/common/format");const{rpc}=require("@web/core/network/rpc");const{patch}=require("@web/core/utils/patch");patch(Message.prototype,{setup(){super.setup(...arguments);this.state.editRating=false;},get ratingValue(){return this.message.rating_value||this.message.rating_id?.rating;},onClikEditComment(){this.state.editRating=!this.state.editRating;if(this.state.editRating){const messageContent=convertBrToLineBreak(this.props.message.rating.publisher_comment);this.props.message.composer={message:this.props.message,text:messageContent,portalComment:true,selection:{start:messageContent.length,end:messageContent.length,direction:"none",},};}},exitEditCommentMode(){this.props.message.composer.clear();this.message.composer=null;this.state.editRating=false;},async deleteComment(){const data=await rpc("/website/rating/comment",{rating_id:this.message.rating.id,publisher_comment:"",});this.message.rating=data;},});return __exports;});;

/* /portal_rating/static/src/chatter/frontend/store_service_patch.js */
odoo.define('@portal_rating/chatter/frontend/store_service_patch',['@mail/core/common/store_service','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Store}=require("@mail/core/common/store_service");const{patch}=require("@web/core/utils/patch");patch(Store.prototype,{async getMessagePostParams({postData}){const params=await super.getMessagePostParams(...arguments);if(postData.rating_value){params.post_data.rating_value=postData.rating_value;}
return params;},});return __exports;});;

/* /portal_rating/static/src/chatter/frontend/thread_model_patch.js */
odoo.define('@portal_rating/chatter/frontend/thread_model_patch',['@mail/core/common/thread_model','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{Thread}=require("@mail/core/common/thread_model");const{patch}=require("@web/core/utils/patch");patch(Thread.prototype,{setup(){super.setup();this.selectedRating;this.rating_stats;},getFetchParams(){const params=super.getFetchParams(...arguments);if(this.model!=="discuss.channel"){params["rating_include"]=true;if(this.selectedRating){params["rating_value"]=this.selectedRating;}}
return params;},});return __exports;});

                    /*******************************************
                    *  Templates                               *
                    *******************************************/

                    odoo.define("portal.assets_chatter.bundle.xml", ["@web/core/templates"], function(require) {
                        "use strict";
                        const { checkPrimaryTemplateParents, registerTemplate, registerTemplateExtension } = require("@web/core/templates");
                        /* portal.assets_chatter */
                        registerTemplate("web.DebugMenu", `/web/static/src/core/debug/debug_menu.xml`, `<t t-name="web.DebugMenu" xml:space="preserve">
        <div class="o_debug_manager">
            <Dropdown beforeOpen.bind="loadGroupedItems" position="'bottom-end'">
                <button t-att-class="\`o-dropdown--narrow \${env.inDialog?'btn btn-link':''}\`">
                    <i class="fa fa-bug" role="img" aria-label="Open developer tools"/>
                </button>
                <t t-set-slot="content">
                    <t t-foreach="sectionEntries" t-as="entry" t-key="entry[0]">
                        <div class="dropdown-menu_group dropdown-header">
                            <t t-esc="getSectionLabel(entry[0])"/>
                        </div>
                        <t t-foreach="entry[1]" t-as="element" t-key="element_index">
                            <DropdownItem t-if="element.type == 'item'" onSelected="element.callback" attrs="{ href: element.href }">
                                <span t-att-style="entry[0] and 'padding-left: 12px;'" t-esc="element.description"/>
                            </DropdownItem>
                            <t t-if="element.type == 'component'" t-component="element.Component" t-props="element.props"/>
                        </t>
                    </t>
                </t>
            </Dropdown>
        </div>
    </t>

`);
registerTemplate("web.DebugMenu.SetDefaultDialog", `/web/static/src/core/debug/debug_menu_items.xml`, `<t t-name="web.DebugMenu.SetDefaultDialog" xml:space="preserve">
        <Dialog title.translate="Set Default Values">
            <table style="width: 100%">
                <tr>
                    <td>
                        <label for="formview_default_fields" class="oe_label oe_align_right">
                            Default:
                        </label>
                    </td>
                    <td class="oe_form_required">
                        <select id="formview_default_fields" class="o_input" t-model="state.fieldToSet">
                            <option value=""/>
                            <option t-foreach="defaultFields" t-as="field" t-att-value="field.name" t-key="field.name">
                                <t t-esc="field.string"/> = <t t-esc="field.displayed"/>
                            </option>
                        </select>
                    </td>
                </tr>
                <tr t-if="conditions.length">
                    <td>
                        <label for="formview_default_conditions" class="oe_label oe_align_right">
                            Condition:
                        </label>
                    </td>
                    <td>
                        <select id="formview_default_conditions" class="o_input" t-model="state.condition">
                            <option value=""/>
                            <option t-foreach="conditions" t-as="cond" t-att-value="cond.name + '=' + cond.value" t-key="cond.name">
                                <t t-esc="cond.string"/>=<t t-esc="cond.displayed"/>
                            </option>
                        </select>
                    </td>
                </tr>
                <tr>
                    <td colspan="2">
                        <input type="radio" id="formview_default_self" value="self" name="scope" t-model="state.scope"/>
                        <label for="formview_default_self" class="oe_label" style="display: inline;">
                            Only you
                        </label>
                        <br/>
                        <input type="radio" id="formview_default_all" value="all" name="scope" t-model="state.scope"/>
                        <label for="formview_default_all" class="oe_label" style="display: inline;">
                            All users
                        </label>
                    </td>
                </tr>
            </table>
            <t t-set-slot="footer">
                <button class="btn btn-secondary" t-on-click="props.close">Close</button>
                <button class="btn btn-secondary" t-on-click="saveDefault">Save default</button>
            </t>
        </Dialog>
    </t>

    `);
registerTemplate("web.DebugMenu.GetMetadataDialog", `/web/static/src/core/debug/debug_menu_items.xml`, `<t t-name="web.DebugMenu.GetMetadataDialog" xml:space="preserve">
        <Dialog title.translate="Metadata">
            <table class="table table-sm table-striped">
                <tr>
                    <th>ID:</th>
                    <td><t t-esc="state.id"/></td>
                </tr>
                <tr>
                    <th>XML ID:</th>
                    <td>
                        <t t-if="state.xmlids.length &gt; 1">
                            <t t-foreach="state.xmlids" t-as="imd" t-key="imd['xmlid']">
                                <div t-att-class="&quot;p-0 &quot; + (imd[&quot;xmlid&quot;] === state.xmlid ? &quot;fw-bolder &quot; : &quot;&quot;) + (imd[&quot;noupdate&quot;] === true ? &quot;fst-italic &quot; : &quot;&quot;)" t-esc="imd['xmlid']"/>
                            </t>
                        </t>
                        <t t-elif="state.xmlid" t-esc="state.xmlid"/>
                        <t t-else="">
                            / <a t-on-click="onClickCreateXmlid"> (create)</a>
                        </t>
                    </td>
                </tr>
                <tr>
                    <th>No Update:</th>
                    <td>
                        <t t-esc="state.noupdate"/>
                        <t t-if="state.xmlid">
                            <a t-on-click="toggleNoupdate"> (change)</a>
                        </t>
                    </td>
                </tr>
                <tr>
                    <th>Creation User:</th>
                    <td><t t-esc="state.creator"/></td>
                </tr>
                <tr>
                    <th>Creation Date:</th>
                    <td><t t-esc="state.createDate"/></td>
                </tr>
                <tr>
                    <th>Latest Modification by:</th>
                    <td><t t-esc="state.lastModifiedBy"/></td>
                </tr>
                <tr>
                    <th>Latest Modification Date:</th>
                    <td><t t-esc="state.writeDate"/></td>
                </tr>
            </table>
        </Dialog>
    </t>

    `);
registerTemplate("web.DebugMenu.GetViewDialog", `/web/static/src/core/debug/debug_menu_items.xml`, `<t t-name="web.DebugMenu.GetViewDialog" xml:space="preserve">
        <Dialog title.translate="Get View">
            <pre t-esc="props.arch"/>
            <t t-set-slot="footer">
                <button class="btn btn-primary o-default-button" t-on-click="() =&gt; props.close()">Close</button>
            </t>
        </Dialog>
    </t>
`);
registerTemplate("web.ActionMenus", `/web/static/src/search/action_menus/action_menus.xml`, `<t t-name="web.ActionMenus" xml:space="preserve">
        <div class="o_cp_action_menus d-flex pe-2 gap-1">
            <div t-if="props.items.print?.length" class="d-inline-block">
                <Dropdown beforeOpen.bind="loadPrintItems">
                    <button class="btn btn-secondary" data-hotkey="shift+u">
                        <i class="fa fa-print me-1"/>
                        <span class="o_dropdown_title" t-out="props.printDropdownTitle"/>
                    </button>
                    <t t-set-slot="content">
                        <t t-foreach="state.printItems" t-as="item" t-key="item.key">
                            <DropdownItem class="item.class" onSelected="() =&gt; this.onItemSelected(item)" t-out="item.description"/>
                        </t>
                    </t>
                </Dropdown>
            </div>

            <div t-if="actionItems.length" class="d-inline-block">
                <Dropdown>
                    <button class="btn btn-secondary" data-hotkey="u">
                        <i class="fa fa-cog me-1"/>
                        <span class="o_dropdown_title">Actions</span>
                    </button>
                    <t t-set-slot="content">
                        <t t-foreach="actionItems" t-as="item" t-key="item.key">
                            <t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
                                <div role="separator" class="dropdown-divider"/>
                            </t>
                            <t t-if="item.Component" t-component="item.Component" t-props="item.props"/>
                            <DropdownItem t-else="" class="'o_menu_item'" onSelected="() =&gt; this.onItemSelected(item)">
                                <i t-if="item.icon" t-att-class="item.icon + ' me-1 fa-fw oi-fw'"/>
                                <t t-esc="item.description"/>
                            </DropdownItem>
                            <t t-set="currentGroup" t-value="item.groupNumber"/>
                        </t>
                    </t>
                </Dropdown>
        </div>
        </div>
    </t>

`);
registerTemplate("web.Breadcrumbs", `/web/static/src/search/breadcrumbs/breadcrumbs.xml`, `<t t-name="web.Breadcrumbs" xml:space="preserve">
        <t t-set="currentBreadcrumbs" t-value="props.breadcrumbs.slice(-1)"/>
        <t t-set="visiblePathBreadcrumbs" t-value="props.breadcrumbs.slice(-3, -1)"/>
        <t t-set="collapsedBreadcrumbs" t-value="props.breadcrumbs.slice(0, -3).reverse()"/>
        <t t-set="breadcrumb" t-value="currentBreadcrumbs[0] || {}"/>

        <div t-if="collapsedBreadcrumbs.length || visiblePathBreadcrumbs.length" class="o_breadcrumb d-flex flex-row flex-md-column align-self-stretch justify-content-between min-w-0">
            <t t-if="env.isSmall">
                <t t-set="previousBreadcrumb" t-value="visiblePathBreadcrumbs.at(-1)"/>
                <button class="o_back_button btn btn-link d-print-none px-1 py-0" t-on-click.prevent="previousBreadcrumb.onSelected">
                    <i class="oi oi-fw oi-arrow-left"/>
                </button>
            </t>
            <t t-else="">
                <ol class="breadcrumb flex-nowrap text-nowrap lh-sm">
                    <li t-if="collapsedBreadcrumbs.length" class="breadcrumb-item d-inline-flex min-w-0">
                        <Dropdown>
                            <button class="btn btn-light btn-sm px-1 p-0">
                                <i class="fa fa-ellipsis-h"/>
                            </button>
                            <t t-set-slot="content">
                                <t t-foreach="collapsedBreadcrumbs" t-as="breadcrumb" t-key="breadcrumb.jsId">
                                    <DropdownItem onSelected="breadcrumb.onSelected" attrs="{ href: breadcrumb.url, 'data-tooltip': getBreadcrumbTooltip(breadcrumb)}">
                                        <t t-call="web.Breadcrumb.Name"/>
                                    </DropdownItem>
                                </t>
                            </t>
                        </Dropdown>
                    </li>
                    <t t-foreach="visiblePathBreadcrumbs" t-as="breadcrumb" t-key="breadcrumb.jsId">
                        <li class="breadcrumb-item d-inline-flex min-w-0" t-att-class="{ o_back_button: breadcrumb_last }" t-att-data-hotkey="breadcrumb_last and 'b'" t-on-click.prevent="breadcrumb.onSelected">
                            <a t-att-href="breadcrumb.url" class="fw-bold text-truncate" t-att-data-tooltip="'Back to &quot;' + breadcrumb.name + '&quot;' + (breadcrumb.isFormView ? ' form' : '')"><t t-call="web.Breadcrumb.Name"/></a>
                        </li>
                    </t>
                </ol>
            </t>
            <div class="d-flex gap-1 text-truncate">
                <div class="o_last_breadcrumb_item active d-flex gap-2 align-items-center min-w-0 lh-sm">
                    <span class="min-w-0 text-truncate" t-call="web.Breadcrumb.Name"/>
                </div>
                <t t-call="web.Breadcrumb.Actions"/>
            </div>
        </div>

        <div t-else="" class="o_breadcrumb d-flex gap-1 text-truncate">
            <div class="o_last_breadcrumb_item active d-flex fs-4 min-w-0 align-items-center">
                <span class="min-w-0 text-truncate" t-call="web.Breadcrumb.Name"/>
            </div>
            <t t-call="web.Breadcrumb.Actions"/>
        </div>

        <t t-slot="breadcrumb-status-indicator"/>
    </t>

    `);
registerTemplate("web.Breadcrumb.Actions", `/web/static/src/search/breadcrumbs/breadcrumbs.xml`, `<t t-name="web.Breadcrumb.Actions" xml:space="preserve">
        <div class="o_control_panel_breadcrumbs_actions d-inline-flex d-print-none">
            <t t-slot="breadcrumb-additional-actions"/>
        </div>
    </t>

    `);
registerTemplate("web.Breadcrumb.Name", `/web/static/src/search/breadcrumbs/breadcrumbs.xml`, `<t t-name="web.Breadcrumb.Name" xml:space="preserve">
        <t t-if="breadcrumb.name" t-out="breadcrumb.name"/>
        <em t-else="" class="text-warning">Unnamed</em>
    </t>

`);
registerTemplate("web.CogMenu", `/web/static/src/search/cog_menu/cog_menu.xml`, `<t t-name="web.CogMenu" xml:space="preserve">
        <div t-if="hasItems" class="o_cp_action_menus d-flex align-items-center gap-1" t-att-class="{'pe-2': !env.isSmall}">
            <div class="lh-1">
                <Dropdown menuClass="'lh-base'" beforeOpen.bind="loadPrintItems">
                    <button class="d-print-none btn" t-att-class="env.isSmall ? 'btn-secondary' : 'lh-sm p-0 border-0'" data-hotkey="u" data-tooltip="Actions">
                        <i class="fa fa-fw fa-cog"/>
                    </button>

                    <t t-set-slot="content">
                        <t t-if="state.printItems.length">
                            <Dropdown t-if="state.printItems.length &gt; 1">
                                <button>
                                    <i class="fa fa-print me-1"/> <t t-out="props.printDropdownTitle"/>
                                </button>
                                <t t-set-slot="content">
                                    <DropdownItem t-foreach="state.printItems" t-as="item" t-key="item.key" class="'o_menu_item'" onSelected="() =&gt; this.onItemSelected(item)">
                                        <t t-esc="item.description"/>
                                    </DropdownItem>
                                </t>
                            </Dropdown>

                            <DropdownItem t-else="" class="'o_menu_item'" onSelected="() =&gt; this.onItemSelected(state.printItems[0])">
                                <i class="fa fa-print me-1"/> <t t-out="state.printItems[0].description"/>
                            </DropdownItem>
                        </t>

                        <t t-foreach="cogItems" t-as="item" t-key="item.key">
                            <t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
                                <div role="separator" class="dropdown-divider"/>
                            </t>

                            <t t-if="item.Component" t-component="item.Component" t-props="item.props"/>

                            <DropdownItem t-else="" class="'o_menu_item'" onSelected="() =&gt; this.onItemSelected(item)">
                                <i t-if="item.icon" t-att-class="item.icon" class="fa-fw oi-fw me-1"/>
                                <t t-esc="item.description"/>
                            </DropdownItem>

                            <t t-set="currentGroup" t-value="item.groupNumber"/>
                        </t>
                    </t>
                </Dropdown>
            </div>
        </div>
    </t>

`);
registerTemplate("web.ControlPanel", `/web/static/src/search/control_panel/control_panel.xml`, `<t t-name="web.ControlPanel" xml:space="preserve">
        <div class="o_control_panel d-flex flex-column gap-3 px-3 pt-2 pb-3" t-ref="root" data-command-category="actions">
            <Transition t-if="!env.isSmall" visible="state.embeddedInfos.showEmbedded" name="'o-fade'" t-slot-scope="transition" leaveDuration="500">
                <div class="o_embedded_actions d-flex flex-wrap w-100 align-items-center justify-content-center gap-2" t-att-class="transition.className">
                    <t t-foreach="state.embeddedInfos.embeddedActions" t-as="action" t-key="action.id">
                        <t t-if="_checkValueLocalStorage(action)">
                            <button class="btn btn-secondary o_draggable" t-att-class="{ 'active': state.embeddedInfos.currentEmbeddedAction?.id === action.id}" t-on-click="() =&gt; this.onEmbeddedActionClick(action)" t-att-data-id="action.id">
                                <span t-out="action.name"/>
                            </button>
                        </t>
                    </t>
                    <t t-call="web.embeddedActionsDropdown"/>
                </div>
            </Transition>
            <div class="o_control_panel_main d-flex flex-wrap flex-lg-nowrap justify-content-between align-items-lg-start gap-2 gap-lg-3 flex-grow-1">
                <div class="o_control_panel_breadcrumbs d-flex align-items-center gap-1 order-0 h-lg-100">
                    <div class="o_control_panel_main_buttons d-flex gap-1 d-empty-none d-print-none" t-ref="mainButtons" t-on-keydown="onMainButtonsKeydown">
                        <div t-if="env.isSmall" class="btn-group o_control_panel_collapsed_create">
                            <t t-slot="control-panel-create-button"/>
                            <button t-att-class="{invisible: display.disableDropdown}" type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split o_control_panel_collapsed_create" data-bs-toggle="dropdown" aria-expanded="false">
                                 <span class="visually-hidden">Toggle Dropdown</span>
                            </button>
                            <ul class="dropdown-menu o_control_panel_collapsed_create">
                                <t t-slot="layout-buttons"/>
                                <t t-slot="control-panel-always-buttons"/>
                            </ul>
                        </div>
                        <div t-else="" class="d-inline-flex gap-1">
                            <t t-slot="control-panel-create-button"/>
                            <t t-slot="layout-buttons"/>
                            <t t-slot="control-panel-always-buttons"/>
                        </div>
                    </div>
                    <t t-if="env.config.noBreadcrumbs">
                        <section class="o_control_panel_breadcrumbs_actions d-contents d-print-none">
                            <t t-slot="control-panel-additional-actions"/>
                            <t t-slot="control-panel-status-indicator"/>
                        </section>
                    </t>
                    <t t-elif="env.isSmall">

                        <div class="o_fallback_breadcrumbs d-contents"/>
                        <Breadcrumbs breadcrumbs="breadcrumbs" t-portal="'.o_navbar_breadcrumbs, .o_fallback_breadcrumbs'"/>
                        <section class="o_control_panel_breadcrumbs_actions d-contents">
                            <t t-slot="control-panel-additional-actions"/>
                            <t t-slot="control-panel-status-indicator"/>
                        </section>
                    </t>
                    <t t-else="">
                        <Breadcrumbs breadcrumbs="breadcrumbs">
                            <t t-set-slot="breadcrumb-status-indicator">
                                <t t-slot="control-panel-status-indicator"/>
                            </t>
                            <t t-set-slot="breadcrumb-additional-actions">
                                <t t-slot="control-panel-additional-actions"/>
                            </t>
                        </Breadcrumbs>
                    </t>
                    <div t-if="!env.isSmall" class="me-auto"/> 
                </div>

                <div class="o_control_panel_actions d-empty-none d-flex align-items-center justify-content-start justify-content-lg-around order-2 order-lg-1 w-100 mw-100 w-lg-auto">
                    <t t-if="display.layoutActions" t-slot="layout-actions"/>
                    <t t-slot="control-panel-selection-actions"/>
                </div>

                <div class="o_control_panel_navigation d-flex flex-wrap flex-md-nowrap justify-content-end gap-1 gap-xl-3 order-1 order-lg-2 flex-grow-1">
                    <t t-if="state.embeddedInfos.embeddedActions?.length and env.isSmall">
                        <t t-call="web.embeddedActionsDropdown"/>
                    </t>
                    <div t-if="pagerProps and pagerProps.total &gt; 0" class="o_cp_pager text-nowrap " role="search">
                        <Pager t-props="pagerProps"/>
                    </div>
                    <button t-if="state.embeddedInfos.embeddedActions?.length and !env.isSmall" class="btn btn-secondary" t-att-class="{active: state.embeddedInfos.showEmbedded}" t-on-click="onClickShowEmbedded">
                        <i class="fa fa-sliders"/>
                    </button>
                    <t t-if="env.config.viewSwitcherEntries?.length &gt; 1">
                        <div t-if="env.isSmall" class="o_cp_switch_buttons btn-group d-print-none">
                            <Dropdown>
                                <button class="btn btn-secondary">
                                    <t t-set="activeView" t-value="env.config.viewSwitcherEntries.find((view) =&gt; view.active)"/>
                                    <i class="oi-fw" t-att-class="activeView.icon"/>
                                </button>
                                <t t-set-slot="content">
                                    <t t-foreach="env.config.viewSwitcherEntries" t-as="view" t-key="view.type">
                                        <DropdownItem onSelected="() =&gt; this.switchView(view.type)" class="view.active ? 'selected' : ''">
                                            <i class="oi-fw" t-att-class="view.icon"/>
                                            <span class="ms-1" t-out="view.name"/>
                                        </DropdownItem>
                                    </t>
                                </t>
                            </Dropdown>
                        </div>
                        <nav t-else="" class="o_cp_switch_buttons d-print-none d-inline-flex btn-group">
                            <t t-foreach="env.config.viewSwitcherEntries" t-as="view" t-key="view.type">
                                <button class="btn btn-secondary o_switch_view " t-attf-class="o_{{view.type}} {{view.active ? 'active' : ''}}" t-att-data-tooltip="view.name" t-on-click="() =&gt; this.switchView(view.type)">
                                    <i t-att-class="view.icon"/>
                                </button>
                            </t>
                        </nav>
                    </t>
                    <t t-slot="control-panel-navigation-additional"/>
                </div>
            </div>
        </div>
    </t>

    `);
registerTemplate("web.embeddedActionsDropdown", `/web/static/src/search/control_panel/control_panel.xml`, `<t t-name="web.embeddedActionsDropdown" xml:space="preserve">
        <Dropdown menuClass="'o_embedded_actions_dropdown_menu'">
            <button class="btn btn-secondary">
                <i class="fa fa-fw fa-sliders"/>
            </button>
            <t t-set-slot="content">
                <t t-foreach="state.embeddedInfos.embeddedActions" t-as="action" t-key="action.id">
                    <DropdownItem class="this.getDropdownClass(action)" onSelected="() =&gt; this.env.isSmall ? this.onEmbeddedActionClick(action) : this._setVisibility(action.id)" closingMode="'none'">
                        <div class="d-flex p-0 pe-1 align-items-center justify-content-between">
                            <span t-out="action.name"/>
                            <i t-if="action.is_deletable" class="o_icon_right btn btn-link text-danger p-0 position-absolute end-0 fa fa-trash-o" title="Delete item" t-on-click.stop="() =&gt; this.openConfirmationDialog(action)"/>
                        </div>
                    </DropdownItem>
                </t>
                <div role="separator" class="dropdown-divider"/>
                <AccordionItem class="'o_save_current_view'" description.translate="Save View">
                    <div class="px-2 py-2">
                        <input type="text" t-ref="newActionNameRef" class="o_input mb-2" t-model.trim="state.embeddedInfos.newActionName"/>
                        <CheckBox value="state.embeddedInfos.newActionIsShared" onChange.bind="_onShareCheckboxChange">
                            <span data-tooltip="Make this embedded action available to other users">Shared</span>
                        </CheckBox>
                    </div>
                    <div class="px-3 py-2">
                        <button class="o_save_favorite btn btn-primary w-100" t-on-click="_saveNewAction">
                            Save
                        </button>
                    </div>
                </AccordionItem>
            </t>
        </Dropdown>
    </t>

`);
registerTemplate("web.CustomFavoriteItem", `/web/static/src/search/custom_favorite_item/custom_favorite_item.xml`, `<t t-name="web.CustomFavoriteItem" xml:space="preserve">
        <AccordionItem class="'o_add_favorite text-truncate'" description.translate="Save current search">
            <div class="px-3 py-2">
                <input type="text" class="o_input" t-ref="description" t-model.trim="state.description" t-on-keydown="onInputKeydown"/>
                <CheckBox value="state.isDefault" onChange.bind="onDefaultCheckboxChange">
                    <span data-tooltip="Use this filter by default when opening this view">Default filter</span>
                </CheckBox>
                <CheckBox value="state.isShared" onChange.bind="onShareCheckboxChange">
                    <span data-tooltip="Make this filter available to other users">Shared</span>
                </CheckBox>
            </div>
            <div class="px-3 py-2">
                <button class="o_save_favorite btn btn-primary w-100" t-on-click="saveFavorite">
                    Save
                </button>
            </div>
        </AccordionItem>
    </t>

`);
registerTemplate("web.CustomGroupByItem", `/web/static/src/search/custom_group_by_item/custom_group_by_item.xml`, `<t t-name="web.CustomGroupByItem" xml:space="preserve">
        <select class="o_add_custom_group_menu o_menu_item dropdown-item o-navigable" t-on-change="(ev) =&gt; this.onSelected(ev)">
            <option value="" disabled="true" selected="true" hidden="true">Add Custom Group</option>
            <option t-foreach="props.fields" t-as="field" t-key="field.name" t-if="field.type !== 'properties' and !field.isProperty" t-att-value="field.name" t-esc="field.string"/>
        </select>
    </t>

`);
registerTemplate("web.Layout", `/web/static/src/search/layout.xml`, `<t t-name="web.Layout" xml:space="preserve">
        <t t-if="env.inDialog" t-portal="'#' + env.dialogId + ' .modal-footer'">
            <t t-slot="layout-buttons"/>
        </t>
        <t t-component="components.ControlPanel" slots="controlPanelSlots" t-if="props.display.controlPanel" display="props.display.controlPanel"/>
        <div t-ref="content" class="o_content" t-attf-class="{{props.className}}" t-att-class="{ 'o_component_with_search_panel': props.display.searchPanel }">
            <t t-component="components.SearchPanel" t-if="props.display.searchPanel"/>
            <t t-slot="default" contentRef="contentRef"/>
        </div>
    </t>

`);
registerTemplate("web.PropertiesGroupByItem", `/web/static/src/search/properties_group_by_item/properties_group_by_item.xml`, `<t t-name="web.PropertiesGroupByItem" xml:space="preserve">
        <AccordionItem class="'o_add_custom_group_menu text-truncate'" description="props.item.description" selected="isActive">
            <t t-set="items" t-value="state.groupByItems"/>
            <t t-if="items and items.length">
                <t t-foreach="items" t-as="item" t-key="item.name">
                    <t t-set="description" t-value="isSingleParent ? item.description : \`\${item.description} (\${item.definitionRecordName})\`"/>

                    <AccordionItem t-if="item.options" description="description" selected="item.isActive">
                        <t t-foreach="item.options" t-as="option" t-key="option.id">
                            <t t-set="optionName" t-value="\`\${item.name}:\${option.id}\`"/>
                            <CheckboxItem class="{ o_item_option: true, selected: option.isActive }" checked="option.isActive" closingMode="'none'" t-esc="option.description" onSelected="() =&gt; this.onGroup({ itemId: item.id, optionId: option.id})"/>
                        </t>
                    </AccordionItem>

                    <CheckboxItem t-else="" class="{ o_menu_item: true, selected: item.isActive }" checked="item.isActive" closingMode="'none'" t-esc="description" onSelected="() =&gt; this.onGroup({ itemId: item.id })"/>
                </t>
            </t>
            <DropdownItem t-else="" closingMode="'none'" class="'fst-italic'">
                No Properties
            </DropdownItem>
        </AccordionItem>
    </t>
`);
registerTemplate("web.SearchBar.Facets", `/web/static/src/search/search_bar/search_bar.xml`, `<t t-name="web.SearchBar.Facets" xml:space="preserve">
        <t t-foreach="env.searchModel.facets" t-as="facet" t-key="facet_index">
            <div class="o_searchview_facet position-relative d-inline-flex align-items-stretch rounded-2 bg-200 text-nowrap opacity-trigger-hover mw-100" t-att-class="{o_facet_with_domain: facet.domain }" role="listitem" aria-label="search" tabindex="0" t-on-keydown="ev =&gt; this.onFacetKeydown(facet, facet_index, ev)">


                <div class="position-absolute start-0 top-0 bottom-0 end-0 bg-view border rounded-2 shadow opacity-0 opacity-100-hover"/>

                <div class="o_searchview_facet_label position-relative rounded-start-2 px-1 rounded-end-0 p-0" t-on-click="(ev) =&gt; this.onFacetLabelClick(ev.target, facet)" t-att-role="facet.domain ? 'button' : 'img'" t-att-class="{                         'text-bg-action': facet.type == 'groupBy' || facet.type == 'comparison',                         'btn': facet.type == 'groupBy' and env.searchModel.canOrderByCount,                         'btn btn-primary': facet.type == 'field' || facet.type == 'filter',                         'btn btn-favourite': facet.type == 'favorite'                     }">
                    <i t-if="facet.icon" class="small fa-fw" t-att-class="facet.icon" role="image"/>
                    <small t-else="" class="px-1" t-esc="facet.title"/>


                    <span t-if="facet.domain" class="position-absolute start-0 top-0 bottom-0 end-0 bg-inherit opacity-0 opacity-100-hover" t-att-class="{'px-2 transition-base': !facet.icon}">
                        <i class="fa fa-fw fa-cog"/>
                    </span>
                    <span t-if="env.searchModel.canOrderByCount and facet.type === 'groupBy' and !env.searchModel.orderByCount" class="position-absolute start-0 top-0 bottom-0 end-0 bg-inherit opacity-0 opacity-100-hover" t-att-class="{'px-2 transition-base': !facet.icon}">
                        <i class="fa fa-fw fa-sort"/>
                    </span>
                </div>

                <div class="o_facet_values position-relative d-flex flex-wrap align-items-center ps-2 rounded-end-2 text-wrap overflow-hidden">
                    <t t-foreach="facet.values" t-as="facetValue" t-key="facetValue_index">
                        <em t-if="!facetValue_first" class="o_facet_values_sep small fw-bold mx-1 opacity-50" t-esc="facet.separator"/>
                        <small class="o_facet_value text-truncate" t-esc="facetValue" t-att-title="facetValue"/>
                    </t>
                    <button class="o_facet_remove oi oi-close btn btn-link py-0 px-2 text-danger d-print-none" role="button" aria-label="Remove" title="Remove" t-on-click="() =&gt; this.onFacetRemove(facet)"/>
                </div>
            </div>
        </t>
    </t>

    `);
registerTemplate("web.SearchBar.Input", `/web/static/src/search/search_bar/search_bar.xml`, `<t t-name="web.SearchBar.Input" xml:space="preserve">
        <input type="text" class="o_searchview_input o_input d-print-none flex-grow-1 w-auto border-0" accesskey="Q" placeholder="Search..." role="searchbox" t-ref="autofocus" t-on-keydown="onSearchKeydown" t-on-click="onSearchClick" t-on-input="onSearchInput"/>
    </t>

    `);
registerTemplate("web.SearchBar.Items", `/web/static/src/search/search_bar/search_bar.xml`, `<t t-name="web.SearchBar.Items" xml:space="preserve">
        <ul class="o-dropdown--menu dropdown-menu o_searchview_autocomplete show" role="menu">
            <t t-foreach="items" t-as="item" t-key="item.id">
                <li class="o_menu_item dropdown-item" t-att-class="{ o_indent: item.isChild, focus: item_index === state.focusedIndex}" t-att-id="item.id" t-on-click="() =&gt; this.selectItem(item)" t-on-mousemove="() =&gt; this.onItemMousemove(item_index)">
                    <t t-if="item.isParent">
                        <a t-att-class="{ o_expand: true, 'ms-4': item.isFieldProperty}" href="#" t-on-click.stop.prevent="() =&gt; this.toggleItem(item, !item.isExpanded)">
                            <i t-attf-class="fa fa-caret-{{ item.isExpanded ? 'down' : 'right' }}"/>
                        </a>
                    </t>
                    <a href="#" t-on-click.prevent="" t-att-class="{'ps-3 pe-2 text-truncate': item.isFieldProperty }" t-att-title="item.title">
                        <t t-if="item.isAddCustomFilterButton"><span class="text-action">Add Custom Filter</span></t>
                        <t t-elif="item.loadMore"><span class="text-action"><t t-esc="item.label"/></span></t>
                        <t t-elif="item.isChild">
                            <t t-esc="item.label"/>
                        </t>
                        <t t-elif="!item.isFieldProperty"> Search </t> <b t-esc="item.searchItemDescription"/> <t t-if="item.preposition"> <t t-esc="item.preposition"/>: <b class="fst-italic text-primary" t-esc="item.label"/> </t>
                    </a>
                </li>
            </t>
        </ul>
    </t>

    `);
registerTemplate("web.SearchBar", `/web/static/src/search/search_bar/search_bar.xml`, `<t t-name="web.SearchBar" xml:space="preserve">
        <div t-if="visibilityState.showSearchBar" class="o_cp_searchview d-flex input-group" role="search" t-ref="root">
            <div class="o_searchview form-control d-print-contents d-flex align-items-center py-1 border-end-0" role="search" aria-autocomplete="list">
                <button class="d-print-none btn border-0 p-0" role="button" aria-label="Search..." title="Search..." t-on-click="this.onClickSearchIcon">
                    <i class="o_searchview_icon oi oi-search me-2" role="img"/>
                </button>
                <div class="o_searchview_input_container d-flex flex-grow-1 flex-wrap gap-1 mw-100">
                    <t t-call="web.SearchBar.Facets"/>
                    <t t-call="web.SearchBar.Input"/>
                    <t t-if="items.length">
                        <t t-call="web.SearchBar.Items"/>
                    </t>
                </div>
            </div>
            <SearchBarMenu dropdownState="searchBarDropdownState">
                <t t-slot="search-bar-additional-menu"/>
            </SearchBarMenu>
        </div>
    </t>

`);
registerTemplate("web.SearchBar.Toggler", `/web/static/src/search/search_bar/search_bar_toggler.xml`, `<t t-name="web.SearchBar.Toggler" xml:space="preserve">
        <button t-if="props.isSmall" t-attf-class="btn btn-secondary {{ props.showSearchBar ? 'active' : '' }}" t-on-click="props.toggleSearchBar">
            <i class="fa fa-fw fa-search"/>
        </button>
    </t>

`);
registerTemplate("web.SearchBarMenu", `/web/static/src/search/search_bar_menu/search_bar_menu.xml`, `<t t-name="web.SearchBarMenu" xml:space="preserve">
        <Dropdown menuClass="'o_search_bar_menu d-flex flex-wrap flex-lg-nowrap w-100 w-md-auto mx-md-auto mt-2 py-3'" position="'bottom-end'" state="this.props.dropdownState" t-if="this.env.searchModel.searchMenuTypes.size">
            <button class="o_searchview_dropdown_toggler d-print-none btn btn-outline-secondary o-dropdown-caret rounded-start-0" data-hotkey="shift+q" title="Toggle Search Panel">
            </button>
            <t t-set-slot="content">

                <t t-if="this.env.searchModel.searchMenuTypes.has('filter')">
                    <div class="o_dropdown_container o_filter_menu w-100 w-lg-auto h-100 px-3 mb-4 mb-lg-0 border-end">
                        <div class="px-3 fs-5 mb-2">
                            <i class="me-2 text-primary" t-att-class="facet_icons.filter"/>
                            <h5 class="o_dropdown_title d-inline">Filters</h5>
                        </div>
                        <t t-set="currentGroup" t-value="null"/>
                        <t t-foreach="filterItems" t-as="item" t-key="item.id">
                            <t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
                                <div class="dropdown-divider" role="separator"/>
                            </t>
                            <t t-if="item.options">
                                <AccordionItem class="'text-truncate'" description="item.description" selected="item.isActive">
                                    <t t-set="subGroup" t-value="null"/>
                                    <t t-foreach="item.options" t-as="option" t-key="option.id">
                                        <t t-if="subGroup !== null and subGroup !== option.groupNumber">
                                            <div class="dropdown-divider" role="separator"/>
                                        </t>
                                        <CheckboxItem class="{ o_item_option: true, selected: option.isActive }" t-esc="option.description" checked="option.isActive" closingMode="'none'" onSelected="() =&gt; this.onFilterSelected({ itemId: item.id, optionId: option.id })"/>
                                        <t t-set="subGroup" t-value="option.groupNumber"/>
                                    </t>
                                </AccordionItem>
                            </t>
                            <t t-else="">
                                <CheckboxItem class="{ 'o_menu_item text-truncate': true, selected: item.isActive }" checked="item.isActive" closingMode="'none'" t-esc="item.description" attrs="{ title: item.description.length &gt; 15 ? item.description : ''}" onSelected="() =&gt; this.onFilterSelected({ itemId: item.id })"/>
                            </t>
                            <t t-set="currentGroup" t-value="item.groupNumber"/>
                        </t>
                        <t t-if="filterItems.length">
                            <div role="separator" class="dropdown-divider"/>
                        </t>
                        <DropdownItem class="'o_menu_item o_add_custom_filter'" onSelected.bind="onAddCustomFilterClick">Add Custom Filter</DropdownItem>
                    </div>
                </t>

                <t t-if="this.env.searchModel.searchMenuTypes.has('groupBy')">
                    <div class="o_dropdown_container o_group_by_menu w-100 w-lg-auto h-100 px-3 mb-4 mb-lg-0 border-end">
                        <div class="px-3 fs-5 mb-2">
                            <i class="me-2 text-action" t-att-class="facet_icons.groupBy"/>
                            <h5 class="o_dropdown_title d-inline">Group By</h5>
                        </div>
                        <t t-set="currentGroup" t-value="null"/>
                        <t t-foreach="groupByItems" t-as="item" t-key="item.id">
                            <t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
                                <div class="dropdown-divider" role="separator"/>
                            </t>
                            <t t-if="item.fieldType === 'properties'">
                                <PropertiesGroupByItem item="item" onGroup.bind="onGroupBySelected"/>
                            </t>
                            <t t-elif="item.options">
                                <AccordionItem class="'text-truncate'" description="item.description" selected="item.isActive">
                                    <t t-set="subGroup" t-value="null"/>
                                    <t t-foreach="item.options" t-as="option" t-key="option.id">
                                        <t t-if="subGroup !== null and subGroup !== option.groupNumber">
                                            <div class="dropdown-divider" role="separator"/>
                                        </t>
                                        <CheckboxItem class="{ o_item_option: true, selected: option.isActive }" checked="option.isActive ? true : false" closingMode="'none'" t-esc="option.description" attrs="{ title: option.description.length &gt; 15 ? option.description : ''}" onSelected="() =&gt; this.onGroupBySelected({ itemId: item.id, optionId: option.id})"/>
                                        <t t-set="subGroup" t-value="option.groupNumber"/>
                                    </t>
                                </AccordionItem>
                            </t>
                            <t t-else="">
                                <CheckboxItem class="{ 'o_menu_item text-truncate': true, selected: item.isActive }" checked="item.isActive" closingMode="'none'" t-esc="item.description" attrs="{ title: item.description.length &gt; 15 ? item.description : ''}" onSelected="() =&gt; this.onGroupBySelected({ itemId: item.id })"/>
                            </t>
                            <t t-set="currentGroup" t-value="item.groupNumber"/>
                        </t>
                        <t t-if="!hideCustomGroupBy and fields.length">
                            <div t-if="groupByItems.length" role="separator" class="dropdown-divider"/>
                            <CustomGroupByItem fields="fields" onAddCustomGroup.bind="onAddCustomGroup"/>
                        </t>
                    </div>
                </t>

                <t t-if="showComparisonMenu">
                    <div class="o_dropdown_container o_comparison_menu w-100 w-lg-auto h-100 px-3 border-end">
                        <div class="px-3 fs-5 mb-2">
                            <i class="me-2 text-danger" t-att-class="facet_icons.comparison"/>
                            <h5 class="o_dropdown_title d-inline">Comparison</h5>
                        </div>
                        <t t-foreach="comparisonItems" t-as="item" t-key="item.id">
                            <CheckboxItem class="{ 'o_menu_item text-truncate': true, selected: item.isActive }" checked="item.isActive" closingMode="'none'" t-esc="item.description" attrs="{ title: item.description.length &gt; 15 ? item.description : ''}" onSelected="() =&gt; this.onComparisonSelected(item.id)"/>
                        </t>
                    </div>
                </t>

                <t t-if="this.env.searchModel.searchMenuTypes.has('favorite')">
                    <div class="o_dropdown_container o_favorite_menu w-100 w-lg-auto h-100 px-3">
                        <t t-set="sharedFavoritesItems" t-value="sharedFavorites"/>
                        <t t-set="favoriteItems" t-value="favorites"/>
                        <div class="px-3 fs-5 mb-2">
                            <i class="me-2 text-favourite" t-att-class="facet_icons.favorite"/>
                            <h5 class="o_dropdown_title d-inline">Favorites</h5>
                        </div>
                        <t t-foreach="favoriteItems" t-as="item" t-key="item.id">
                            <t t-call="web.SearchBarMenu.FavoriteItem"/>
                        </t>
                        <t t-if="favoriteItems.length">
                            <div role="separator" class="dropdown-divider"/>
                        </t>
                        <t t-if="sharedFavoritesItems.length &gt; 10">
                            <AccordionItem class="'o_shared_favorites text-truncate'" description.translate="Shared filters">
                                <t t-foreach="sharedFavoritesItems" t-as="item" t-key="item.id">
                                    <t t-call="web.SearchBarMenu.FavoriteItem"/>
                                </t>
                            </AccordionItem>
                        </t>
                        <t t-else="">
                            <t t-foreach="sharedFavoritesItems" t-as="item" t-key="item.id">
                                <t t-call="web.SearchBarMenu.FavoriteItem"/>
                            </t>
                        </t>
                        <div t-if="sharedFavoritesItems.length" role="separator" class="dropdown-divider"/>
                        <t t-set="currentGroup" t-value="null"/>
                        <t t-foreach="otherItems" t-as="item" t-key="item.key">
                            <t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
                                <div role="separator" class="dropdown-divider"/>
                            </t>
                            <t t-component="item.Component"/>
                            <t t-set="currentGroup" t-value="item.groupNumber"/>
                        </t>
                    </div>
                </t>
                <t t-slot="default"/>
            </t>
        </Dropdown>
    </t>

    `);
registerTemplate("web.SearchBarMenu.FavoriteItem", `/web/static/src/search/search_bar_menu/search_bar_menu.xml`, `<t t-name="web.SearchBarMenu.FavoriteItem" xml:space="preserve">
        <t t-set="item" t-value="item"/>
        <CheckboxItem class="{ 'o_menu_item text-truncate': true, selected: item.isActive }" checked="item.isActive" closingMode="'none'" onSelected="() =&gt; this.onFavoriteSelected(item.id)">
            <span class="d-flex p-0 align-items-center justify-content-between">
                <span t-esc="item.description" t-att-title="item.description.length &gt; 15 ? item.description : ''" class="text-truncate flex-grow-1"/>
                <i class="ms-1 fa fa-trash-o" title="Delete item" t-on-click.stop="() =&gt; this.openConfirmationDialog(item.id, item.userId)"/>
            </span>
        </CheckboxItem>
    </t>

`);
registerTemplate("web.SearchPanel", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanel" xml:space="preserve">
    <t t-if="env.isSmall">
        <t t-call="web.SearchPanel.Small"/>
    </t>
    <t t-else="">
        <t t-if="state.sidebarExpanded" t-call="web.SearchPanel.Regular"/>
        <t t-else="" t-call="web.SearchPanel.Sidebar"/>
    </t>
</t>

`);
registerTemplate("web.SearchPanel.Sidebar", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanel.Sidebar" xml:space="preserve">
    <div class="bg-view h-100 o_search_panel_sidebar cursor-pointer d-print-none" t-on-click="toggleSidebar">
        <t t-set="categories" t-value="getCategorySelection()"/>
        <t t-set="filters" t-value="getFilterSelection()"/>
        <div class="d-flex">
            <button class="btn btn-light btn-sm m-1 mb-2 p-2">
                <i class="fa fa-fw fa-angle-double-right"/>
            </button>
            <div class="o_search_panel_current_selection text-truncate mx-auto">
                <t t-if="!categories.length and !filters.length">
                    All
                </t>
                <t t-else="">
                    <t t-foreach="categories" t-as="category" t-key="category_index">
                        <span class="o_search_panel_category mb-2">
                            <i t-if="category.icon" t-attf-class="o_search_panel_section_icon fa {{ category.icon }} fa-rotate-90 mb-2" t-att-style="category.color and ('color: ' + category.color)"/>
                            <t t-esc="category.values.join(' / ')"/>
                        </span>
                    </t>
                    <t t-foreach="filters" t-as="filter" t-key="filter_index">
                        <span class="o_search_panel_filter mb-2">
                            <i t-if="filter.icon" t-attf-class="o_search_panel_section_icon fa {{ filter.icon }} fa-rotate-90 mb-2" t-att-style="filter.color and ('color: ' + filter.color)"/>
                            <t t-esc="filter.values.join(', ')"/>
                        </span>
                    </t>
                </t>
            </div>
        </div>
    </div>
</t>

`);
registerTemplate("web.SearchPanelContent", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanelContent" xml:space="preserve">
    <div class="o_search_panel flex-grow-0 flex-shrink-0 h-100 pb-5 bg-view overflow-auto position-relative" t-att-class="env.searchModel.searchPanelInfo.className" t-attf-class="#{env.isSmall ? 'px-3' : 'pe-1 ps-3'}" t-ref="root">
        <button t-if="!env.isSmall" class="btn btn-light btn-sm end-0 m-2 position-absolute px-2 py-1 top-0 z-1" t-on-click="toggleSidebar">
            <i class="fa fa-fw fa-angle-double-left"/>
        </button>
        <div t-if="!sections or sections.length === 0" class="o_search_panel_empty_state me-3">
            <button class="btn mt-2 w-100 overflow-visible">
                <div class="d-flex align-items-center me-2 ms-auto">All</div>
            </button>
        </div>
        <section t-foreach="sections" t-as="section" t-key="section.id" t-attf-class="o_search_panel_section o_search_panel_{{ section.type }}">
            <header class="o_search_panel_section_header pt-4 pb-2 text-uppercase cursor-default">
                <i t-attf-class="fa {{ section.icon }} o_search_panel_section_icon {{!section.color &amp;&amp; section.type == 'filter' ? 'text-warning' : !section.color ? 'text-primary': ''}} me-2" t-att-style="section.color and ('color: ' + section.color)"/>
                <b t-esc="section.description"/>
            </header>
            <t t-call="web.SearchPanel.Section"/>
        </section>
    </div>
    <div class="h-100">
        <span class="o_search_panel_resize" t-on-click.stop.prevent="" t-on-pointerdown.stop.prevent="_onStartResize"/>
    </div>
</t>

`);
registerTemplate("web.SearchPanel.Section", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanel.Section" xml:space="preserve">
    <div t-if="section.errorMsg" class="alert alert-warning">
        <span><t t-esc="section.errorMsg"/></span>
    </div>
    <ul t-else="" class="list-group d-block o_search_panel_field px-2 px-md-0">
        <t t-if="section.type === 'category'" t-call="{{ constructor.subTemplates.category }}">
            <t t-set="values" t-value="section.rootIds"/>
        </t>
        <t t-elif="section.groups">
            <li t-foreach="section.sortedGroupIds" t-as="groupId" t-key="groupId" class="o_search_panel_filter_group list-group-item p-0 border-0" t-att-class="groupId_last? 'mb-0' : 'mb-3'">

                <t t-set="_section" t-value="section"/>
                <t t-set="group" t-value="section.groups.get(groupId)"/>
                <header class="o_search_panel_group_header pb-1">
                    <div class="form-check w-100">

                        <input type="checkbox" class="form-check-input" t-attf-id="{{ section.id }}_input_{{ groupId }})" t-on-click="() =&gt; this.toggleFilterGroup(section.id, group)"/>
                        <label t-attf-for="{{ section.id }}_input_{{ groupId }})" class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 cursor-pointer" t-att-class="{ o_with_counters: group.enableCounters }" t-att-title="group.tooltip or false">
                            <span class="o_search_panel_label_title text-truncate" t-attf-class="{{ group.color_index ? 'o_tag o_badge badge rounded-pill o_tag_color_' + group.color_index : ''}}">
                                <t t-esc="group.name"/>
                            </span>
                        </label>
                    </div>
                </header>
                <ul class="list-group d-block">
                    <t t-call="{{ constructor.subTemplates.filtersGroup }}">
                        <t t-set="values" t-value="group.values"/>
                        <t t-set="isChildList" t-value="true"/>

                        <t t-set="section" t-value="_section"/>
                    </t>
                </ul>
            </li>
            <ul t-if="section.groups.get(false)" class="list-group d-block">
                <t t-call="{{ constructor.subTemplates.filtersGroup }}">
                    <t t-set="group" t-value="section.groups.get(false)"/>
                    <t t-set="values" t-value="group.values"/>

                    <t t-set="section" t-value="section"/>
                </t>
            </ul>
        </t>
        <t t-else="" t-call="{{ constructor.subTemplates.filtersGroup }}">
            <t t-set="values" t-value="section.values"/>
        </t>
    </ul>
</t>

`);
registerTemplate("web.SearchPanel.Regular", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanel.Regular" t-inherit="web.SearchPanelContent" t-inherit-mode="primary" xml:space="preserve"/>

`);
registerTemplate("web.SearchPanel.Small", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanel.Small" xml:space="preserve">
    <div class="o_search_panel w-100 overflow-visible" t-ref="root">
        <div class="d-flex overflow-auto align-items-center px-2 border-top">
            <div t-if="!sections or sections.length === 0" class="o_search_panel_empty_state me-3">
                <button class="btn w-100">
                    <div class="d-flex align-items-center me-2 ms-auto">All</div>
                </button>
            </div>
            <Dropdown t-foreach="sections" t-as="section" t-key="section.id" state="getDropdownState(section.id)" menuClass="'my-2 mx-1'" onOpened.bind="updateGroupHeadersChecked">
                <span class="btn btn-secondary my-2 mx-1 o-dropdown-caret">
                    <i t-attf-class="fa {{ section.icon }} o_search_panel_section_icon {{!section.color &amp;&amp; section.type == 'filter' ? 'text-warning' : !section.color ? 'text-primary': ''}} me-2" t-att-style="section.color and ('color: ' + section.color)"/>
                    <b class="pe-2" t-if="section.type !== 'category' || !state.active[section.id]" t-esc="section.description"/>
                    <b class="pe-2" t-else="" t-esc="section.values.get(section.activeValueId)['display_name']"/>
                </span>
                <t t-set-slot="content">
                    <div t-attf-class="o_search_panel_{{ section.type }}" class="o_search_panel_section">
                        <t t-call="web.SearchPanel.Section"/>
                    </div>
                    <div t-if="section.type !== 'category' and hasSelection(section.id)" class="text-end">
                        <a href="#" title="Clear All" class="btn btn-link text-nowrap text-uppercase" t-on-click="() =&gt; this.clearSelection(section.id)">CLEAR ALL</a>
                    </div>
                </t>
            </Dropdown>
            <a href="#" t-if="hasSelection()" title="Clear All" class="btn btn-link text-nowrap text-uppercase" t-on-click="() =&gt; this.clearSelection()">CLEAR ALL</a>
            <div t-else="" class="mx-4"/>
        </div>
    </div>
</t>

`);
registerTemplate("web.SearchPanel.Category", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanel.Category" xml:space="preserve">
    <t t-foreach="values" t-as="valueId" t-key="valueId">
        <t t-set="value" t-value="section.values.get(valueId)"/>
        <li class="o_search_panel_category_value list-group-item py-1 cursor-pointer border-0 pe-0" t-att-class="isChildList ? env.isSmall ? '' : 'o_treeEntry' : 'ps-0'">
            <header class="list-group-item list-group-item-action d-flex align-items-center px-0 py-lg-0 border-0" t-att-class="{'active text-900 fw-bold': state.active[section.id] === valueId}" t-on-click="() =&gt; this.toggleCategory(section, value)">
                <div class="o_search_panel_label d-flex align-items-center overflow-hidden w-100 cursor-pointer mb-0" t-att-class="{'o_with_counters': section.enableCounters }" t-att-data-tooltip="value.display_name">
                    <button class="o_toggle_fold btn p-0 px-1 flex-shrink-0 text-center">
                        <i t-if="value.childrenIds.length" class="fa" t-att-class="{                                 'fa-caret-down' : state.expanded[section.id][valueId],                                 'fa-caret-right':  !state.expanded[section.id][valueId]                             }"/>
                    </button>
                    <span class="o_search_panel_label_title text-truncate" t-att-class="{'fw-bold' : value.bold}" t-esc="value.display_name"/>
                </div>
                <small t-if="section.enableCounters and value.__count gt 0" class="o_search_panel_counter text-muted mx-2 fw-bold" t-esc="value.__count"/>
            </header>
            <ul t-if="value.childrenIds.length and state.expanded[section.id][valueId]" class="list-group d-block">
                <t t-call="{{ constructor.subTemplates.category }}">
                    <t t-set="values" t-value="value.childrenIds"/>
                    <t t-set="isChildList" t-value="true"/>
                </t>
            </ul>
        </li>
    </t>
</t>

`);
registerTemplate("web.SearchPanel.FiltersGroup", `/web/static/src/search/search_panel/search_panel.xml`, `<t t-name="web.SearchPanel.FiltersGroup" xml:space="preserve">
    <li t-foreach="[...values.keys()]" t-as="valueId" t-key="valueId" class="o_search_panel_filter_value list-group-item p-0 mb-1 border-0 cursor-pointer" t-att-class="{ 'ps-2' : isChildList }">
        <t t-set="value" t-value="values.get(valueId)"/>
        <div class="form-check w-100">
            <input type="checkbox" t-attf-id="{{ section.id }}_input_{{ valueId }}" t-att-checked="state.active[section.id][valueId]" class="form-check-input" t-on-click="ev =&gt; this.toggleFilterValue(section.id, valueId, ev)"/>
            <label class="o_search_panel_label form-check-label d-flex align-items-center justify-content-between w-100 cursor-pointer" t-attf-for="{{ section.id }}_input_{{ valueId }}" t-att-title="(group and group.tooltip) or false">
                <span class="o_search_panel_label_title text-truncate" t-esc="value.display_name"/>
                <span t-if="section.enableCounters and value.__count gt 0" class="o_search_panel_counter text-muted mx-2 small" t-esc="value.__count"/>
            </label>
        </div>
    </li>
</t>

`);
registerTemplate("web.WithSearch", `/web/static/src/search/with_search/with_search.xml`, `<t t-name="web.WithSearch" xml:space="preserve">
    <t t-slot="default" context="searchModel.context" domain="searchModel.domain" groupBy="searchModel.groupBy" orderBy="searchModel.orderBy" comparison="searchModel.comparison" display="searchModel.display"/>
  </t>

`);
registerTemplate("web.FileUploader", `/web/static/src/views/fields/file_handler.xml`, `<t t-name="web.FileUploader" xml:space="preserve">
        <t t-if="state.isUploading and props.showUploadingText">Uploading...</t>
        <span t-else="" t-on-click.prevent="onSelectFileButtonClick" style="display:contents">
            <t t-slot="toggler"/>
        </span>
        <t t-slot="default"/>
        <input type="file" t-att-name="props.inputName" t-ref="fileInput" t-attf-class="o_input_file d-none {{ props.fileUploadClass or '' }}" t-att-multiple="props.multiUpload ? 'multiple' : false" t-att-accept="props.acceptedFileExtensions or '*'" t-on-change="onFileChange"/>
    </t>

`);
registerTemplate("mail.AttachmentList", `/mail/static/src/core/common/attachment_list.xml`, `<t t-name="mail.AttachmentList" xml:space="preserve">
        <div class="o-mail-AttachmentList overflow-y-auto d-flex flex-column mt-1" t-att-class="{                 'o-inComposer': env.inComposer,                 'o-inChatWindow': env.inChatWindow,                 'me-2 pe-4': isInChatWindowAndIsAlignedLeft and !env.inComposer,                 'ms-2 ps-4': isInChatWindowAndIsAlignedRight and !env.inComposer,             }">
            <div class="d-flex flex-grow-1 flex-wrap mx-1 align-items-center" t-att-class="{                 'justify-content-end': isInChatWindowAndIsAlignedRight and !env.inComposer,                         }" role="menu">
                <div t-foreach="images" t-as="attachment" t-key="attachment.id" t-att-aria-label="attachment.filename" class="o-mail-AttachmentImage d-flex position-relative flex-shrink-0 mw-100 mb-1 me-1 rounded o-viewable" t-att-title="attachment.name" t-att-class="{ 'o-isUploading': attachment.uploading }" tabindex="0" t-att-data-mimetype="attachment.mimetype" t-on-click="() =&gt; this.fileViewer.open(attachment, props.attachments)" role="menuitem">
                    <img class="img img-fluid my-0 mx-auto rounded" t-att-class="{ 'opacity-25': attachment.uploading }" t-att-src="getImageUrl(attachment)" t-att-alt="attachment.name" t-attf-style="max-width: min(100%, {{ imagesWidth }}px); max-height: {{ props.imagesHeight }}px;" t-on-load="onImageLoaded"/>
                    <div t-if="attachment.uploading" class="position-absolute top-0 bottom-0 start-0 end-0 d-flex align-items-center justify-content-center" title="Uploading">
                        <i class="fa fa-spin fa-spinner"/>
                    </div>
                    <ImageActions actions="getActions(attachment)" imagesHeight="props.imagesHeight"/>
                </div>
            </div>
            <div class="d-flex flex-wrap mt-1 mx-1">

                <div t-foreach="cards" t-as="attachment" t-key="attachment.id" class="o-mail-AttachmentCard d-flex rounded mb-1 me-1 mw-100 overflow-auto" t-att-class="{                                'ms-1': isInChatWindowAndIsAlignedRight,                                'me-1': !isInChatWindowAndIsAlignedRight,                                'o-viewable': attachment.isViewable,                                'o-isUploading': attachment.uploading,                                }" t-attf-class="bg-300" t-att-title="attachment.name ? attachment.name : undefined" role="menu" t-att-aria-label="attachment.name" t-on-click="() =&gt; this.fileViewer.open(attachment, props.attachments)">

                    <t t-ref="nonImageMain">
                        <div class="o-mail-AttachmentCard-image o_image flex-shrink-0 m-1" role="menuitem" aria-label="Preview" t-att-tabindex="-1" t-att-aria-disabled="false" t-att-data-mimetype="attachment.mimetype"/>
                        <div class="overflow-auto d-flex justify-content-center flex-column px-1">
                            <div t-if="attachment.name" class="text-truncate" t-out="props.messageSearch?.highlight(attachment.name) ?? attachment.name"/>
                            <small t-if="attachment.extension" class="text-uppercase" t-esc="attachment.extension"/>
                        </div>
                    </t>
                    <div class="flex-grow-1"/>
                    <div class="o-mail-AttachmentCard-aside position-relative rounded-end overflow-hidden d-flex" t-att-class="{ 'o-hasMultipleActions d-flex flex-column': showDelete and !env.inComposer }">
                        <div t-if="attachment.uploading" class="d-flex justify-content-center align-items-center w-100 h-100" title="Uploading">
                            <i class="fa fa-spin fa-spinner"/>
                        </div>
                        <div t-if="!attachment.uploading and env.inComposer" class="d-flex justify-content-center me-2 align-items-center w-100 h-100 text-primary" title="Uploaded">
                            <i class="fa fa-check"/>
                        </div>
                        <button t-if="showDelete" class="o-mail-AttachmentCard-unlink btn top-0 align-items-center justify-content-center d-flex w-100 h-100 rounded-0 border-0" t-attf-class="{{ env.inComposer ? 'o-inComposer position-absolute btn-primary transition-base justify-content-center' : 'bg-300' }}" t-on-click.stop="() =&gt; this.onClickUnlink(attachment)" title="Remove">
                            <i class="fa fa-trash" role="img" aria-label="Remove"/>
                        </button>
                        <a t-if="attachment.type === 'url'" class="btn d-flex align-items-center justify-content-center w-100 h-100 rounded-0" t-attf-class="bg-300" t-att-href="attachment.url" target="_blank" title="Open Link">
                            <i class="fa fa-external-link" role="img" aria-label="Open Link"/>
                        </a>

                        <button t-elif="canDownload(attachment)" class="btn d-flex align-items-center justify-content-center w-100 h-100 rounded-0" t-attf-class="bg-300" t-att-data-download-url="attachment.downloadUrl" t-on-click.stop="() =&gt; this.onClickDownload(attachment)" title="Download">
                            <i class="fa fa-download" role="img" aria-label="Download"/>
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </t>

    `);
registerTemplate("mail.ImageActions", `/mail/static/src/core/common/attachment_list.xml`, `<t t-name="mail.ImageActions" xml:space="preserve">
        <div class="position-absolute top-0 bottom-0 start-0 end-0 p-1 text-white o-opacity-hoverable opacity-100-hover d-flex align-items-end flax-wrap flex-column" t-att-class="{ 'opacity-0': !actionsMenuState.isOpen }">
            <button t-if="props.actions.length === 1 and props.imagesHeight gt 75" class="btn btn-sm btn-light rounded px-1 py-0" t-att-class="{ 'opacity-75 opacity-100-hover': !isMobileOS }" tabindex="0" t-att-aria-label="props.actions[0].label" t-att-title="props.actions[0].label" role="menuitem" t-on-click.stop="props.actions[0].onSelect">
                <i t-att-class="props.actions[0].icon"/>
            </button>
            <Dropdown t-else="" menuClass="'d-flex flex-column py-0' + (props.actions.length gt 1 ? ' py-0' : '')" state="actionsMenuState" position="'right-start'">
                <button class="btn btn-sm btn-light rounded px-1 py-0" t-att-class="{ 'opacity-75 opacity-100-hover': !isMobileOS }" tabindex="0" aria-label="Actions" title="Actions" role="menuitem">
                    <i class="oi oi-chevron-down"/>
                </button>
                <t t-set-slot="content">
                    <DropdownItem t-foreach="props.actions" t-as="action" t-key="action_index" class="'px-2 py-1 d-flex align-items-center rounded-0'" onSelected="action.onSelect">
                        <i class="fa-fw" t-att-class="action.icon"/>
                        <span class="mx-2" t-esc="action.label"/>
                    </DropdownItem>
                </t>
            </Dropdown>
        </div>
    </t>

`);
registerTemplate("mail.AttachmentView", `/mail/static/src/core/common/attachment_view.xml`, `<t t-name="mail.AttachmentView" xml:space="preserve">
        <div t-if="state.thread.attachmentsInWebClientView.length &gt; 0" class="o-mail-Attachment">
            <div class="o_attachment_control popout d-print-none cursor-pointer" t-on-click="onClickPopout" data-tooltip="Open preview in a separate window." data-tooltip-delay="400">
                <i class="fa fa-window-restore" aria-hidden="Pop out"/>
            </div>
            <t t-if="state.thread.mainAttachment">
                <h3 t-if="!state.thread.mainAttachment.isPdf" class="mt0 mb8 ps-2 text-muted text-center"><t t-esc="displayName"/></h3>
                <div t-if="state.thread.mainAttachment.isImage" class="o-mail-Attachment-imgContainer">
                    <img id="attachment_img" class="img img-fluid d-block" t-att-src="state.thread.mainAttachment.defaultSource"/>
                </div>
                <iframe t-if="state.thread.mainAttachment.isPdf" class="d-print-none mb48" t-att-src="state.thread.mainAttachment.defaultSource" t-ref="iframeViewerPdf"/>
                <t t-if="state.thread.attachmentsInWebClientView.length &gt; 1 or (state.thread.attachmentsInWebClientView.length &gt; 0 and !(state.thread.mainAttachment.isPdf or state.thread.mainAttachment.isImage))">
                    <a class="arrow o_move_previous text-center" href="#" t-on-click.prevent="onClickPrevious">
                        <span class="oi oi-chevron-left"/>
                    </a>
                    <a class="arrow o_move_next text-center" href="#" t-on-click.prevent="onClickNext">
                        <span class="oi oi-chevron-right"/>
                    </a>
                </t>
            </t>
        </div>
    </t>

    `);
registerTemplate("mail.PopoutAttachmentView", `/mail/static/src/core/common/attachment_view.xml`, `<t t-name="mail.PopoutAttachmentView" xml:space="preserve">
        <div class="o-mail-PopoutAttachmentView">
            <t t-call="mail.AttachmentView"/>
        </div>
    </t>

`);
registerTemplate("mail.AutoresizeInput", `/mail/static/src/core/common/autoresize_input.xml`, `<t t-name="mail.AutoresizeInput" xml:space="preserve">
    <input class="o-mail-AutoresizeInput px-1 border-1 text-truncate" t-attf-class="{{ props.className }}" t-att-class="{'o-focused': state.isFocused}" t-att-placeholder="props.placeholder" t-att-disabled="!props.enabled" t-att-title="state.value" t-model="state.value" t-on-keydown="onKeydownInput" t-on-focus="() =&gt; this.state.isFocused = true" t-on-blur="onBlurInput" t-ref="input" type="text"/>
</t>

`);
registerTemplate("mail.ChatBubble", `/mail/static/src/core/common/chat_bubble.xml`, `<t t-name="mail.ChatBubble" xml:space="preserve">
        <Dropdown t-if="!env.embedLivechat" state="preview" position="'left-middle'" menuClass="'bg-view border-0 p-0 overflow-visible rounded-3 mx-1'" arrow="true" manual="true">
            <t t-call="mail.ChatBubble.core"/>
            <t t-set-slot="content">
                <t t-call="mail.ChatBubble.preview"/>
            </t>
        </Dropdown>
        <t t-else="" t-call="mail.ChatBubble.core"/>
    </t>

    `);
registerTemplate("mail.ChatBubble.core", `/mail/static/src/core/common/chat_bubble.xml`, `<t t-name="mail.ChatBubble.core" xml:space="preserve">
        <div class="o-mail-ChatBubble bg-view position-relative" t-att-name="props.chatWindow.displayName" t-att-class="{ 'o-bouncing': state.bouncing, 'position-fixed': env.embedLivechat, 'position-relative': !env.embedLivechat, 'o-active': preview.isOpen }" t-att-style="env.embedLivechat ? \`top: \${ position.top }; left: \${ position.left };\` : ''" t-on-click="() =&gt; props.chatWindow.open()" t-on-animationend="() =&gt; state.bouncing = false" t-ref="root">
            <span class="o-mail-ChatBubble-unreadIndicator position-absolute" t-att-class="{ 'opacity-50': thread?.isUnread and !thread?.importantCounter, 'opacity-0': !thread?.isUnread or thread?.importantCounter }"><i class="fa fa-circle"/></span>
            <div t-if="thread?.importantCounter &gt; 0" class="o-mail-ChatBubble-counter position-absolute badge rounded-pill fw-bold o-discuss-badge shadow" t-out="thread?.importantCounter"/>
            <button t-if="state.showClose and !env.embedLivechat" class="o-mail-ChatBubble-close position-absolute shadow rounded-circle fw-bold bg-view" title="Close Chat Bubble" t-on-click.stop="() =&gt; this.props.chatWindow.close()"><i class="oi oi-close"/></button>
            <ImStatus t-if="thread?.correspondent?.persona?.im_status and thread?.correspondent?.persona?.im_status != 'offline'" className="'o-mail-ChatBubble-status position-absolute o-mail-brighter'" member="thread.correspondent"/>
            <CountryFlag t-if="thread?.showCorrespondentCountry" country="thread.correspondentCountry" class="'o-mail-ChatBubble-country position-absolute bottom-0 border'"/>
            <button class="o-mail-ChatHub-bubbleBtn btn bg-view">
                <img class="o-mail-ChatBubble-avatar rounded-circle o_object_fit_cover" t-att-class="{ 'o-big': env.embedLivechat }" t-att-src="thread?.avatarUrl" alt="Thread image" draggable="false"/>
            </button>
        </div>
    </t>

    `);
registerTemplate("mail.ChatBubble.preview", `/mail/static/src/core/common/chat_bubble.xml`, `<t t-name="mail.ChatBubble.preview" xml:space="preserve">
        <div class="o-mail-ChatBubble-preview o-mail-ChatBubble-menu px-0 py-1 shadow-sm border border-secondary rounded bg-100" t-ref="preview">
            <div class="text-truncate fw-bolder mb-0 mx-2" t-esc="props.chatWindow.displayName"/>
            <div t-if="previewContent" class="text-truncate small mx-2 text-muted">
                <t t-call="mail.message_preview_prefix">
                    <t t-set="message" t-value="thread?.newestPersistentNotEmptyOfAllMessage"/>
                </t>
                <t t-esc="previewContent"/>
            </div>
        </div>
    </t>


    `);
registerTemplate("mail.message_preview_prefix", `/mail/static/src/core/common/chat_bubble.xml`, `<t t-name="mail.message_preview_prefix" xml:space="preserve">
        <t t-if="message.isSelfAuthored">
            <i class="fa fa-mail-reply me-1"/>You:
        </t>
        <t t-elif="!message.author?.eq(thread.correspondent?.persona)">
            <t t-esc="message.author?.name ?? message.email_from"/>:
        </t>
    </t>

`);
registerTemplate("mail.ChatHub", `/mail/static/src/core/common/chat_hub.xml`, `<t t-name="mail.ChatHub" xml:space="preserve">
    <div class="o-mail-ChatHub" t-if="isShown or ui.isSmall">
        <t t-if="!store.chatHub.compact">
            <t t-foreach="chatHub.opened" t-as="cw" t-key="cw.localId">
                <ChatWindow chatWindow="cw" right="env.embedLivechat ? chatHub.WINDOW_GAP : (chatHub.BUBBLE_START + chatHub.BUBBLE + (chatHub.BUBBLE_OUTER*2) + (chatHub.opened.length - cw_index - 1) * (chatHub.WINDOW + chatHub.WINDOW_INBETWEEN * 2))"/>
            </t>
        </t>
        <div class="o-mail-ChatHub-bubbles position-fixed end-0 d-flex flex-column align-content-start justify-content-end align-items-center" t-att-class="{ 'bottom-0': !store.chatHub.compact or compactPosition.top === 'auto', 'o-liftUp': busMonitoring.hasConnectionIssues }" t-ref="bubbles" t-att-style="store.chatHub.compact ? \`top: \${ compactPosition.top }; left: \${ compactPosition.left };\` : ''">
            <div class="d-flex flex-column align-content-start justify-content-end align-items-center gap-1">
                <t t-if="store.chatHub.compact">
                    <t t-call="mail.ChatHub.compactButton"/>
                </t>
                <t t-else="">
                    <Dropdown t-if="(chatHub.opened.length + chatHub.folded.length) gt 0" state="options" position="'top-end'" menuClass="'d-flex flex-column bg-100 shadow-sm m-0 p-0 mb-1 border-secondary'">
                        <button class="o-mail-ChatHub-bubbleBtn btn o-mail-ChatHub-optionsBtn fa fa-ellipsis-h bg-100 mt-1" t-att-class="{ 'opacity-0': !bubblesHover.isHover and !options.isOpen, 'text-500': bubblesHover.isHover and !options.isOpen and !isMobileOS, 'o-active': bubblesHover.isHover or options.isOpen }" title="Chat Options"/>
                        <t t-set-slot="content">
                            <button class="o-mail-ChatHub-option btn border-0 d-flex align-items-center gap-2 rounded-0 fw-normal" t-on-click="() =&gt; chatHub.compact = true"><i class="fa fa-fw fa-eye-slash"/>Hide all conversations</button>
                            <button class="o-mail-ChatHub-option btn border-0 d-flex align-items-center gap-2 rounded-0 fw-normal" t-on-click="() =&gt; chatHub.closeAll()"><i class="oi fa-fw oi-close"/>Close all conversations</button>
                        </t>
                    </Dropdown>
                    <t t-foreach="chatHub.folded.slice(0, chatHub.maxFolded)" t-as="cw" t-key="cw.localId">
                        <ChatBubble chatWindow="cw"/>
                    </t>
                    <t t-if="chatHub.folded.length &gt; chatHub.maxFolded" t-call="mail.ChatHub.hiddenButton"/>
                </t>
            </div>
        </div>
    </div>
</t>

`);
registerTemplate("mail.ChatHub.compactButton", `/mail/static/src/core/common/chat_hub.xml`, `<t t-name="mail.ChatHub.compactButton" xml:space="preserve">

    <Dropdown manual="true">
        <div class="o-mail-ChatHub-bubbleBtn o-mail-ChatHub-compact o-mail-ChatBubble justify-content-center" t-ref="compact">
            <div t-if="compactCounter &gt; 0" class="o-mail-ChatHub-hiddenBtnCounter position-absolute badge rounded-pill fw-bold o-discuss-badge shadow">
                <t t-out="compactCounter"/>
            </div>
            <button class="o-mail-ChatHub-bubbleBtn btn fs-2" t-on-click="expand">
                <i class="o-mail-ChatHub-hiddenBtnIcon d-flex justify-content-center align-items-center btn rounded-circle shadow-sm fa fa-commenting"/>
            </button>
        </div>
    </Dropdown>
</t>

`);
registerTemplate("mail.ChatHub.hiddenButton", `/mail/static/src/core/common/chat_hub.xml`, `<t t-name="mail.ChatHub.hiddenButton" xml:space="preserve">
    <Dropdown t-if="chatHub.folded.length &gt; chatHub.maxFolded" state="more" position="'left-middle'" menuClass="'o-mail-ChatHub-hiddenMenu bg-100 border border-secondary shadow-sm p-0 m-0'" manual="true">
        <div class="o-mail-ChatBubble o-mail-ChatHub-hiddenBtn justify-content-center" t-att-class="{ 'o-active': more.isOpen }" t-on-click="() =&gt; store.chatHub.compact = true" t-ref="more-button">
            <div t-if="hiddenCounter &gt; 0" class="o-mail-ChatHub-hiddenBtnCounter position-absolute badge rounded-pill fw-bold o-discuss-badge shadow">
                <t t-out="hiddenCounter"/>
            </div>
            <button class="o-mail-ChatHub-bubbleBtn btn outline-0">
                <span class="o-mail-ChatHub-hiddenBtnIcon d-flex justify-content-center align-items-center btn rounded-circle shadow-sm fs-2" t-att-class="{ 'o-active': more.isOpen }">+<t t-esc="chatHub.folded.slice(chatHub.maxFolded).length"/></span>
            </button>
        </div>
        <t t-set-slot="content">
            <ul class="m-0 p-0 overflow-auto" role="menu" t-ref="more-menu">
                <t t-foreach="chatHub.folded.slice(chatHub.maxFolded)" t-as="cw" t-key="cw.localId">
                    <li class="o-mail-ChatHub-hiddenItem gap-2 px-2 py-1" role="menuitem" t-att-name="cw.displayName" t-on-click="() =&gt; cw.open()">
                        <img class="o-mail-ChatHub-hiddenAvatar rounded-circle o_object_fit_cover" t-att-src="cw.thread?.avatarUrl" alt="Thread image" draggable="false"/>
                        <p class="text-truncate fw-bold mb-0" t-esc="cw.displayName"/>
                        <div t-if="cw.thread?.importantCounter &gt; 0" class="o-mail-ChatHub-hiddenCounter badge rounded-pill fw-bold o-discuss-badge" style="padding: 3px 6px">
                            <t t-out="cw.thread?.importantCounter"/>
                        </div>
                        <button class="o-mail-ChatHub-hiddenClose o-mail-ChatBubble-close d-flex align-items-center rounded" t-on-click.stop="() =&gt; cw.close()">
                            <i class="oi oi-close"/>
                        </button>
                    </li>
                </t>
            </ul>
        </t>
    </Dropdown>
</t>

`);
registerTemplate("mail.ChatWindow", `/mail/static/src/core/common/chat_window.xml`, `<t t-name="mail.ChatWindow" xml:space="preserve">
    <t t-set="partitionedActions" t-value="threadActions.partition"/>
    <div class="o-mail-ChatWindow fixed-bottom overflow-hidden d-flex flex-column shadow-sm bg-100" t-att-style="style" t-att-class="{             'w-100 h-100 o-mobile': ui.isSmall,             'rounded-4 border border-dark mb-2': !ui.isSmall,         }" t-on-keydown="onKeydown" tabindex="1">
        <div class="o-mail-ChatWindow-header d-flex align-items-center flex-shrink-0 bg-100 z-1 shadow-sm" t-on-click="onClickHeader" t-att-class="{ 'cursor-pointer': !ui.isSmall and thread and !state.actionsDisabled, 'pt-2': !thread }">
            <t t-if="hasActionsMenu">
                <div class="d-flex text-truncate">
                    <Dropdown position="'left-start'" onStateChanged="isOpen =&gt; this.onActionsMenuStateChanged(isOpen)" menuClass="'d-flex flex-column py-0 bg-100 border-secondary'">
                        <button class="o-mail-ChatWindow-command o-actionsMenu btn rounded-0 d-flex align-items-center px-2 py-1 my-0 w-100 rounded-end-0" t-att-class="{ 'rounded-top-3': !ui.isSmall, 'o-active': state.actionsMenuOpened, 'o-hover': actionsMenuButtonHover.isHover and !parentChannelHover.isHover }" t-att-disabled="state.editingName or state.actionsDisabled" t-att-title="actionsMenuTitleText" t-ref="actionsMenuButton">
                            <t t-call="mail.ChatWindow.headerContent"/>
                            <i t-if="!state.editingName" class="fa fa-fw fa-caret-down"/>
                        </button>
                        <t t-set-slot="content">
                            <t t-set="quickActionsInDropdown" t-value="partitionedActions.quick.slice(ui.isSmall ? 2 : 4)"/>
                            <t t-if="quickActionsInDropdown.length &gt; 0">
                                <t t-set="groupBefore" t-value="true"/>
                                <t t-foreach="quickActionsInDropdown" t-as="action" t-key="action.id">
                                    <t t-call="mail.ChatWindow.dropdownAction">
                                        <t t-set="action" t-value="action"/>
                                    </t>
                                </t>
                            </t>
                            <t t-else="" t-set="groupBefore" t-value="false"/>
                            <t t-if="partitionedActions.group.length &gt; 0">
                                <hr t-if="groupBefore" class="mx-2 my-1"/>
                                <t t-set="groupBefore" t-value="true"/>
                                <t t-foreach="partitionedActions.group" t-as="group" t-key="group_index">
                                    <t t-foreach="group" t-as="action" t-key="action.id">
                                        <t t-call="mail.ChatWindow.dropdownAction">
                                            <t t-set="action" t-value="action"/>
                                        </t>
                                    </t>
                                    <hr t-if="!group_last" class="mx-2 my-1"/>
                                </t>
                            </t>
                            <t t-else="" t-set="groupBefore" t-value="false"/>
                            <t t-if="partitionedActions.other.length">
                                <hr t-if="groupBefore" class="mx-2 my-1"/>
                                <t t-foreach="partitionedActions.other" t-as="action" t-key="action.id">
                                    <t t-call="mail.ChatWindow.dropdownAction">
                                        <t t-set="action" t-value="action"/>
                                    </t>
                                </t>
                            </t>
                        </t>
                    </Dropdown>
                </div>
                <AutoresizeInput t-if="state.editingName" className="'text-truncate fw-bold flex-shrink-1 me-1 py-0 ' + (ui.isSmall ? 'fs-4' : 'fs-5')" enabled="true" autofocus="true" onValidate.bind="renameThread" value="props.chatWindow.displayName"/>
                <i t-if="state.editingName" class="fa fa-fw fa-caret-down opacity-50"/>
            </t>
            <t t-else="">
                <t t-call="mail.ChatWindow.headerContent"/>
            </t>
            <div class="flex-grow-1"/>
            <div t-if="thread and thread.importantCounter &gt; 0" class="o-mail-ChatWindow-counter mx-1 badge rounded-pill fw-bold o-discuss-badge" t-ref="needactionCounter">
                <t t-out="thread.importantCounter"/>
            </div>
            <div class="o-mail-ChatWindow-quickActions d-flex flex-shrink-0 me-2 o-gap-0_5">
                <t t-foreach="partitionedActions.quick.slice(0, ui.isSmall ? 2 : 4).reverse()" t-as="action" t-key="action.id" t-call="mail.ChatWindow.quickAction">
                    <t t-if="action_last" t-set="itemClass" t-value="ui.isSmall ? 'mx-2' : ''"/>
                </t>
            </div>
            <t t-if="this.store.inPublicPage and this.store.self.type === 'guest'">
                <button class="btn ps-1" t-if="!state.editingGuestName">
                    <img class="o-mail-Discuss-selfAvatar rounded-circle o_object_fit_cover flex-shrink-0" alt="Avatar" t-att-src="store.self.avatarUrl" t-on-click="() =&gt; state.editingGuestName = true"/>
                </button>
                <AutoresizeInput t-if="state.editingGuestName" className="'py-1 me-2'" autofocus="true" onValidate.bind="renameGuest" value="store.self.name"/>
            </t>
        </div>
        <div t-if="!props.chatWindow.folded or ui.isSmall" class="d-flex flex-column h-100 overflow-auto position-relative bg-inherit" t-att-class="{ 'opacity-50': state.editingName }" t-ref="content">
            <t t-if="thread" name="thread content">
                <div t-if="threadActions.activeAction?.componentCondition" class="h-100" t-attf-class="{{ threadActions.activeAction.panelOuterClass }}">
                    <t t-component="threadActions.activeAction.component" t-props="{ ...threadActions.activeAction.componentProps, thread }"/>
                </div>
                <t t-else="">
                    <Thread isInChatWindow="true" thread="thread" t-key="thread.localId" jumpPresent="state.jumpThreadPresent" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo"/>
                    <div t-if="thread and thread.hasOtherMembersTyping" class="d-flex bg-view position-relative">
                        <div class="o-mail-ChatWindow-typing d-flex px-2 position-absolute bottom-0 start-0 w-100 bg-view align-items-center">
                            <Typing channel="thread" size="'medium'"/>
                        </div>
                    </div>
                    <Composer composer="thread.composer" autofocus="props.chatWindow.autofocus" mode="'compact'" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo" onPostCallback.bind="() =&gt; this.state.jumpThreadPresent++" dropzoneRef="contentRef" type="composerType"/>
                </t>
            </t>
        </div>
    </div>
</t>

`);
registerTemplate("mail.ChatWindow.quickAction", `/mail/static/src/core/common/chat_window.xml`, `<t t-name="mail.ChatWindow.quickAction" xml:space="preserve">
    <button class="o-mail-ChatWindow-command btn d-flex opacity-100-hover align-items-center p-0 o-quick" style="aspect-ratio: 1;" t-att-class="{ 'border border-secondary rounded-circle o-small p-2 my-1': ui.isSmall }" t-attf-class="{{ itemClass }}" t-att-title="action.name" t-att-disabled="state.actionsDisabled" t-on-click.stop="() =&gt; action.onSelect()"><i class="fa-lg" t-attf-class="{{ action.icon }}"/></button>
</t>

`);
registerTemplate("mail.ChatWindow.dropdownAction", `/mail/static/src/core/common/chat_window.xml`, `<t t-name="mail.ChatWindow.dropdownAction" xml:space="preserve">
    <DropdownItem class="'o-mail-ChatWindow-command btn rounded-0 d-flex align-items-center p-2 m-0'" onSelected="() =&gt; action.onSelect()">
        <i t-att-class="action.icon"/>
        <span class="mx-2" t-out="action.name"/>
    </DropdownItem>
</t>

`);
registerTemplate("mail.ChatWindow.headerContent", `/mail/static/src/core/common/chat_window.xml`, `<t t-name="mail.ChatWindow.headerContent" xml:space="preserve">
    <div class="o-mail-ChatWindow-threadAvatar my-0 me-1" t-att-class="{         'py-1': !thread or threadActions.actions.length gt 3,         'py-2': thread and threadActions.actions.length lte 3,         'ms-1': thread,         'ms-3': !thread,         'ps-2': !hasActionsMenu,     }">
        <img t-if="thread" class="rounded o_object_fit_cover" t-att-src="thread.parent_channel_id?.avatarUrl ?? thread.avatarUrl" alt="Thread Image"/>
        <i t-else="" class="fa fa-pencil fa-lg fa-fw py-1"/>
    </div>
    <t t-if="thread?.parent_channel_id">
        <span class="fw-bold ms-1 p-1 opacity-50 opacity-100-hover cursor-pointer o-hover-text-underline rounded fs-6" t-esc="thread.parent_channel_id.displayName" title="Open Channel" t-ref="parentChannel" t-on-click.stop="() =&gt; this.thread.parent_channel_id.open()"/>
        <i class="fa fa-chevron-right o-xsmaller ms-1 text-muted opacity-50"/>
    </t>
    <ThreadIcon t-if="thread and thread.channel_type === 'chat' and thread.correspondent" thread="thread"/>
    <CountryFlag t-if="thread?.showCorrespondentCountry" country="thread.correspondentCountry" class="'o-mail-ChatWindow-country border'"/>
    <div t-if="!state.editingName" class="text-truncate fw-bold border border-transparent mx-1 my-0 py-1" t-att-title="props.chatWindow.displayName" t-esc="props.chatWindow.displayName" t-att-class="{ 'fs-4': ui.isSmall, 'fs-5': !ui.isSmall }"/>
</t>
`);
registerTemplate("mail.Composer", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer" xml:space="preserve">
    <t t-set="compact" t-value="props.mode === 'compact'"/>
    <t t-set="normal" t-value="props.mode === 'normal'"/>
    <t t-set="extended" t-value="props.mode === 'extended'"/>
    <div t-ref="composer">
        <div class="o-mail-Composer d-grid flex-shrink-0 pt-0" t-att-class="{                     'pb-2': extended and !props.composer.message,                     'o-extended': extended,                     'o-isUiSmall': ui.isSmall,                     'px-3 pb-2': normal,                     'o-hasSelfAvatar': !env.inChatWindow and thread,                     'o-focused': props.composer.isFocused,                     'o-editing': props.composer.message,                     'o-chatWindow mx-2': env.inChatWindow,                     'mb-3': env.inChatWindow and !props.composer.message,                     'o-discussApp': env.inDiscussApp,                 }" t-attf-class="{{ props.className }}" t-ref="root">
            <div class="o-mail-Composer-sidebarMain flex-shrink-0" t-if="showComposerAvatar">
                <img class="o-mail-Composer-avatar o_avatar rounded" t-att-src="(thread?.effectiveSelf or message.effectiveSelf).avatarUrl" alt="Avatar of user"/>
            </div>
            <div class="o-mail-Composer-coreHeader text-truncate small p-2" t-if="props.composer.thread and props.messageToReplyTo?.thread?.eq(props.composer.thread)">
                <span class="cursor-pointer" t-on-click="() =&gt; env.messageHighlight?.highlightMessage(props.messageToReplyTo.message, props.composer.thread)">
                    Replying to <b t-esc="props.messageToReplyTo.message.author?.name ?? props.messageToReplyTo.message.email_from"/>
                </span>
                <span t-if="props.messageToReplyTo.message.thread.notEq(props.composer.thread)">
                    on: <b><t t-esc="props.messageToReplyTo.message.thread.displayName"/></b>
                </span>
                <i class="fa fa-lg fa-times-circle rounded-circle p-0 ms-1 cursor-pointer" title="Stop replying" t-on-click="() =&gt; props.messageToReplyTo.cancel()"/>
            </div>
            <div class="o-mail-Composer-coreMain d-flex flex-nowrap align-items-start flex-grow-1" t-att-class="{ 'flex-column' : extended }">
                <div class="d-flex o-mail-Composer-bg m-1 shadow-sm flex-grow-1 border border-secondary rounded-3" t-att-class="{                         'o-iosPwa': isIosPwa,                         'align-self-stretch' : extended,                     }" t-ref="input-container">
                    <div class="position-relative flex-grow-1">
                        <t t-set="inputClasses" t-value="'o-mail-Composer-inputStyle form-control border-0 rounded-3'"/>
                        <textarea class="o-mail-Composer-input o-mail-Composer-bg shadow-none overflow-auto text-body" t-att-class="{ 'o-mobile': isMobileOS }" t-attf-class="{{ inputClasses }}" t-ref="textarea" t-on-keydown="onKeydown" t-on-focusin="onFocusin" t-on-focusout="onFocusout" t-on-click="(ev) =&gt; markEventHandled(ev, 'composer.onClickTextarea')" t-on-paste="onPaste" t-model="props.composer.text" t-att-placeholder="placeholder" t-att-readOnly="!state.active"/>

                        <textarea class="o-mail-Composer-fake position-absolute overflow-hidden" t-att-class="{ 'o-mobile': isMobileOS }" t-attf-class="{{ inputClasses }}" t-model="props.composer.text" t-ref="fakeTextarea" disabled="1"/>
                    </div>
                    <t t-if="!extended and !props.composer.message" t-call="mail.Composer.actions"/>
                    <t t-else="" t-call="mail.Composer.quickActions"/>
                </div>
                <div t-if="extended and !props.composer.message" class="d-flex align-items-center mt-2 gap-1 w-100 px-2">
                    <t t-call="mail.Composer.sendButton"/>
                    <span t-if="!isSendButtonDisabled and !props.composer.message and !isMobileOS" class="text-muted small ms-1" t-out="OR_PRESS_SEND_KEYBIND"/>
                    <span class="flex-grow-1"/>
                    <t t-call="mail.Composer.extraActions"/>
                </div>
            </div>
            <div class="o-mail-Composer-footer overflow-auto">
                <AttachmentList t-if="allowUpload and props.composer.attachments.length &gt; 0" attachments="props.composer.attachments" unlinkAttachment.bind="(...args) =&gt; attachmentUploader.unlink(...args)" imagesHeight="75"/>
                <Picker t-props="picker"/>
            </div>
        </div>
        <div t-if="props.composer.message" class="d-flex align-items-center gap-1 w-100 pe-2">
            <span t-if="props.composer.message" class="text-muted px-1 small" t-out="CANCEL_OR_SAVE_EDIT_TEXT" t-on-click="onClickCancelOrSaveEditText"/>
            <span class="flex-grow-1"/>
            <t t-call="mail.Composer.extraActions"/>
        </div>
    </div>
    <NavigableList t-if="suggestion" class="'o-mail-Composer-suggestionList'" t-props="navigableListProps"/>
</t>

`);
registerTemplate("mail.Composer.sendButton", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.sendButton" xml:space="preserve">
    <button class="o-mail-Composer-send btn" t-att-class="{             'btn-primary btn-sm': extended,             'btn-link rounded-circle p-0': !extended,             'me-2': env.inDiscussApp,             'border-start-0': env.inDiscussApp and !props.composer.message,             'border-0': props.composer.message,         }" t-on-click="sendMessage" t-att-disabled="isSendButtonDisabled" t-att-aria-label="SEND_TEXT">
        <t t-if="thread and thread.model !== 'discuss.channel'" t-out="SEND_TEXT"/>
        <t t-else=""><i class="fa fa-fw fa-lg fa-paper-plane-o"/></t>
    </button>
</t>

`);
registerTemplate("mail.Composer.fullComposer", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.fullComposer" xml:space="preserve">
    <button t-if="props.showFullComposer and thread and thread.model !== 'discuss.channel'" class="o-mail-Composer-fullComposer btn border-0 rounded-circle p-0" title="Full composer" aria-label="Full composer" type="button" t-on-click="onClickFullComposer" data-hotkey="shift+c">
        <i class="fa fa-fw fa-lg fa-expand"/>
    </button>
</t>

`);
registerTemplate("mail.Composer.actions", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.actions" xml:space="preserve">
    <div class="o-mail-Composer-actions d-flex" name="root" t-att-class="{             'ms-1': compact and ui.isSmall,             'mx-1': compact and !ui.isSmall,             'mx-2': extended,             'ms-3': normal,             'rounded-3': !props.composer.message,             'o-mail-Composer-bg': !extended and !props.composer.message,         }">
        <div class="o-mail-Composer-mainActions d-flex flex-grow-1 align-items-baseline gap-1" t-att-class="{ 'mt-2': !props.composer.message and !env.inChatWindow, 'mt-1': env.inChatWindow }" t-ref="main-actions">
            <t t-call="mail.Composer.emojiPicker"/>
            <t t-call="mail.Composer.attachFiles"/>
            <t t-if="ui.isSmall and props.composer.message" t-call="mail.Composer.sendButton"/>
            <t t-if="!extended" t-call="mail.Composer.fullComposer"/>
            <t t-if="hasSendButtonNonEditing" t-call="mail.Composer.sendButton"/>
        </div>
        <t t-if="extended" t-call="mail.Composer.fullComposer"/>
    </div>
</t>

`);
registerTemplate("mail.Composer.quickActions", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.quickActions" xml:space="preserve">
    <div class="o-mail-Composer-actions o-mail-Composer-quickActions d-flex rounded o-mail-Composer-bg" name="root" t-att-class="{ 'me-1': props.composer.message, 'me-2': extended }">
        <div class="o-mail-Composer-mainActions d-flex flex-grow-1 align-items-baseline mt-2" t-ref="main-actions">
            <t t-call="mail.Composer.emojiPicker"/>
        </div>
    </div>
</t>

`);
registerTemplate("mail.Composer.extraActions", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.extraActions" xml:space="preserve">
    <div class="o-mail-Composer-actions d-flex px-1 gap-1" name="root" t-att-class="{ 'mx-1': props.composer.message }">
        <div class="o-mail-Composer-mainActions d-flex flex-grow-1 align-items-baseline">
            <t t-call="mail.Composer.attachFiles"/>
            <t t-if="ui.isSmall and props.composer.message" t-call="mail.Composer.sendButton"/>
            <t t-if="!extended" t-call="mail.Composer.fullComposer"/>
            <t t-if="hasSendButtonNonEditing" t-call="mail.Composer.sendButton"/>
        </div>
        <t t-if="extended" t-call="mail.Composer.fullComposer"/>
    </div>
</t>

`);
registerTemplate("mail.Composer.emojiPicker", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.emojiPicker" xml:space="preserve">
    <button class="btn border-0 rounded-circle p-0" t-att-class="{'opacity-100': this.picker.state.picker === this.picker.PICKERS.EMOJI}" aria-label="Emojis" t-on-click="onClickAddEmoji" t-ref="emoji-button"><i class="fa fa-fw fa-lg fa-smile-o"/></button>
</t>

`);
registerTemplate("mail.Composer.attachFiles", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.attachFiles" xml:space="preserve">
    <FileUploader t-if="allowUpload" multiUpload="true" onUploaded.bind="(data) =&gt; { attachmentUploader.uploadData(data) }">
        <t t-set-slot="toggler">
            <button t-att-disabled="!state.active" class="o-mail-Composer-attachFiles btn border-0 rounded-circle p-0" title="Attach files" aria-label="Attach files" type="button" t-on-click="onClickAddAttachment"><i class="fa fa-fw fa-lg fa-paperclip"/></button>
        </t>
    </FileUploader>
</t>

    `);
registerTemplate("mail.Composer.suggestionSpecial", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.suggestionSpecial" xml:space="preserve">
        <strong class="px-2 py-1 align-self-center flex-shrink-0 text-truncate">
            <t t-esc="option.displayName"/>
        </strong>
        <em class="text-600 text-truncate align-self-center">
            <t t-esc="option.description"/>
        </em>
    </t>

    `);
registerTemplate("mail.Composer.suggestionPartner", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.suggestionPartner" xml:space="preserve">
        <t t-set="partner" t-value="option.partner"/>
        <ImStatus t-if="partner" persona="partner"/>
        <t t-set="title" t-value="partner.name"/>
        <t t-if="partner.email" t-set="title" t-value="\`\${title} (\${partner.email})\`"/>
        <strong class="o-mail-Composer-suggestionPartner-name px-2 py-1 align-self-center flex-shrink-0 text-truncate" t-att-class="{'o-has-email': partner.email, 'mw-100': !partner.email}" t-att-title="title">
            <t t-esc="partner.name"/>
        </strong>
        <span t-if="partner.email" class="text-600 text-truncate align-self-center" t-att-title="title">(<t t-esc="partner.email"/>)</span>
    </t>

    `);
registerTemplate("mail.Composer.suggestionThread", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.suggestionThread" xml:space="preserve">
        <strong class="px-2 py-1 align-self-center flex-shrink-0 text-truncate">
            <i t-attf-class="fa #{option.thread.parent_channel_id ? 'fa-comments-o' : 'fa-hashtag'} me-2"/>
            <t t-if="option.thread.parent_channel_id">
                <t t-esc="option.thread.parent_channel_id.displayName"/>
                <i class="oi oi-chevron-right o-xsmaller mx-1"/>
            </t>
            <t t-esc="option.thread.displayName"/>
        </strong>
    </t>

    `);
registerTemplate("mail.Composer.suggestionChannelCommand", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.suggestionChannelCommand" xml:space="preserve">
        <strong class="px-2 py-1 align-self-center flex-shrink-0 text-truncate">
            <t t-esc="option.label"/>
        </strong>
        <span class="text-600 text-truncate align-self-center">
            <t t-esc="option.help"/>
        </span>
    </t>

    `);
registerTemplate("mail.Composer.suggestionCannedResponse", `/mail/static/src/core/common/composer.xml`, `<t t-name="mail.Composer.suggestionCannedResponse" xml:space="preserve">
        <strong class="px-2 py-1 align-self-center flex-shrink-1 text-truncate">
            <t t-esc="option.source"/>
        </strong>
        <span class="text-600 text-truncate align-self-center" style="flex-basis: 20%;">
            <t t-esc="option.label"/>
        </span>
    </t>
`);
registerTemplate("mail.CountryFlag", `/mail/static/src/core/common/country_flag.xml`, `<t t-name="mail.CountryFlag" xml:space="preserve">
        <img class="o-mail-CountryFlag" t-att-class="props.class" t-att-title="props.country.name" t-att-src="props.country.flagUrl"/>
    </t>

`);
registerTemplate("mail.DateSection", `/mail/static/src/core/common/date_section.xml`, `<t t-name="mail.DateSection" xml:space="preserve">
    <div class="o-mail-DateSection d-flex align-items-center w-100 fw-bold z-1" t-attf-class="{{ props.className }}">
        <hr class="o-discuss-separator flex-grow-1"/>
        <span class="px-2 smaller text-muted opacity-75" t-att-class="{ 'user-select-none': isMobileOS }"><t t-esc="props.date"/></span>
        <hr class="o-discuss-separator flex-grow-1"/>
    </div>
</t>
`);
registerTemplate("mail.ImStatus", `/mail/static/src/core/common/im_status.xml`, `<t t-name="mail.ImStatus" xml:space="preserve">
        <div class="o-mail-ImStatus d-flex justify-content-center flex-shrink-0 align-items-center rounded-circle bg-inherit" t-attf-class="{{ props.className }}" t-att-style="props.style" t-att-class="{             'o-md': props.size === 'md' or props.size === 'medium',             'o-sm': props.size === 'sm' or props.size === 'small',         }">
            <span class="d-flex flex-column" name="icon" t-att-class="{                 'smaller': props.size === 'md' or props.size === 'medium',                 'o-xsmaller': props.size === 'sm' or props.size === 'small',             }">
                <t t-if="(!props.member or !props.member.isTyping) and persona">
                    <i t-if="persona.im_status === 'online'" class="fa fa-circle text-success" title="Online" role="img" aria-label="User is online"/>
                    <i t-elif="persona.im_status === 'away'" class="fa fa-circle o-away" title="Idle" role="img" aria-label="User is idle"/>
                    <i t-elif="persona.im_status === 'offline'" class="fa fa-circle-o text-700 opacity-75" title="Offline" role="img" aria-label="User is offline"/>
                    <i t-elif="persona.im_status === 'bot'" class="fa fa-heart text-success" title="Bot" role="img" aria-label="User is a bot"/>
                    <i t-else="" class="fa fa-fw fa-question-circle opacity-75" title="No IM status available"/>
                </t>
                <Typing t-if="props.member?.isTyping" member="props.member" channel="props.member.threadAsTyping" size="'medium'" displayText="false"/>
            </span>
        </div>
    </t>

`);
registerTemplate("mail.LinkPreview", `/mail/static/src/core/common/link_preview.xml`, `<t t-name="mail.LinkPreview" xml:space="preserve">
        <t t-if="props.linkPreview.isCard">
            <t t-call="mail.LinkPreviewCard"/>
        </t>
        <t t-if="props.linkPreview.isVideo">
            <t t-call="mail.LinkPreviewVideo"/>
        </t>
        <t t-if="props.linkPreview.isImage">
            <t t-call="mail.LinkPreviewImage"/>
        </t>
    </t>

    `);
registerTemplate("mail.LinkPreviewCard", `/mail/static/src/core/common/link_preview.xml`, `<t t-name="mail.LinkPreviewCard" xml:space="preserve">
        <div class="o-mail-LinkPreviewCard card position-relative w-100 mb-2 rounded bg-view shadow-sm overflow-hidden" t-att-class="{ 'me-2': env.inChatter }" t-attf-class="{{ className }}">
            <div class="row g-0 flex-row-reverse h-100">
                <div class="col min-w-0" t-att-class="{ 'd-flex align-items-center': !props.linkPreview.og_description }">
                    <div class="p-3">
                        <h6 class="card-title mb-0 me-2" t-attf-class="{{ props.linkPreview.og_description ? 'text-truncate' : 'overflow-hidden' }}">
                            <a t-att-href="props.linkPreview.source_url" target="_blank" t-out="props.linkPreview.og_title"/>
                        </h6>
                        <span t-if="props.linkPreview.og_site_name" t-out="props.linkPreview.og_site_name"/>
                        <p t-if="props.linkPreview.og_description" class="o-mail-LinkPreviewCard-description card-text mb-0 mt-2 text-muted small overflow-hidden" t-out="props.linkPreview.og_description"/>
                    </div>
                </div>
                <div class="o-mail-LinkPreviewCard-imageLinkWrap col-4 d-block">
                    <a t-att-href="props.linkPreview.source_url" target="_blank">
                        <img t-if="props.linkPreview.og_image" class="w-100 h-100 o_object_fit_cover" t-att-src="props.linkPreview.og_image" t-att-alt="props.linkPreview.og_title" t-on-load="onImageLoaded"/>
                        <span t-else="" class="d-flex align-items-center justify-content-center w-100 h-100 bg-100 text-300">
                            <i class="fa fa-camera fa-2x"/>
                        </span>
                    </a>
                </div>
            </div>
            <t t-if="props.deletable" t-call="mail.LinkPreview.aside">
                <t t-set="className" t-value="'fa fa-stack p-0 opacity-75 opacity-100-hover'"/>
            </t>
        </div>
    </t>

    `);
registerTemplate("mail.LinkPreviewImage", `/mail/static/src/core/common/link_preview.xml`, `<t t-name="mail.LinkPreviewImage" xml:space="preserve">
        <div class="o-mail-LinkPreviewImage position-relative mb-2 rounded" t-att-class="{ 'me-2': env.inChatter }" t-attf-class="{{ className }}">
            <a t-if="props.linkPreview.imageUrl" t-att-href="props.linkPreview.imageUrl" target="_blank">
                <img class="h-auto w-auto rounded" t-att-src="props.linkPreview.imageUrl" t-on-load="onImageLoaded"/>
            </a>
            <t t-if="props.deletable" t-call="mail.LinkPreview.aside">
                <t t-set="className" t-value="'btn btn-sm btn-dark mt-2 me-2 rounded opacity-75 opacity-100-hover'"/>
            </t>
        </div>
    </t>

    `);
registerTemplate("mail.LinkPreviewVideo", `/mail/static/src/core/common/link_preview.xml`, `<t t-name="mail.LinkPreviewVideo" xml:space="preserve">
        <div class="o-mail-LinkPreviewVideo card position-relative w-100 mb-2 rounded bg-view shadow-sm overflow-hidden" t-att-class="{ 'me-2': env.inChatter }" t-attf-class="{{ className }}">
            <div class="row g-0 flex-row-reverse h-100">
                <div class="col min-w-0" t-att-class="{ 'd-flex align-items-center': !props.linkPreview.og_description }">
                    <div class="p-3 bg-view">
                        <h6 class="o-mail-LinkPreviewVideo-title card-title mb-0 me-2" t-attf-class="{{ props.linkPreview.og_description ? 'text-truncate' : 'o-mail-LinkPreviewVideo-hasDescription overflow-hidden' }}">
                            <a t-att-href="props.linkPreview.source_url" target="_blank" t-esc="props.linkPreview.og_title"/>
                        </h6>
                        <p t-if="props.linkPreview.og_description" class="o-mail-LinkPreviewVideo-description o-mail-LinkPreviewVideo-hasDescription card-text mb-0 mt-2 text-muted small overflow-hidden" t-out="props.linkPreview.og_description"/>
                    </div>
                </div>
                <div class="o-mail-LinkPreviewVideo-videoWrap col-4 d-block">
                    <a t-att-href="props.linkPreview.source_url" target="_blank">
                        <div t-if="props.linkPreview.og_image" class="o-mail-LinkPreviewVideo-overlay position-relative h-100 opacity-trigger-hover">
                            <img class="img-fluid h-100 rounded-bottom o_object_fit_cover" t-att-src="props.linkPreview.og_image" t-att-alt="props.linkPreview.og_title" t-on-load="onImageLoaded"/>
                            <div class="position-absolute top-50 start-50 translate-middle">
                                <div class="o-mail-LinkPreviewVideo-play btn btn-lg rounded opacity-75 opacity-100-hover transition-base">
                                    <i class="fa fa-play"/>
                                </div>
                            </div>
                        </div>
                        <span t-else="" class="d-flex align-items-center justify-content-center w-100 h-100 bg-100 text-300">
                            <i class="fa fa-camera fa-2x"/>
                        </span>
                    </a>
                </div>
            </div>
            <t t-if="props.deletable" t-call="mail.LinkPreview.aside">
                <t t-set="className" t-value="'fa fa-stack p-0 opacity-75 opacity-100-hover'"/>
            </t>
        </div>
    </t>

    `);
registerTemplate("mail.LinkPreview.aside", `/mail/static/src/core/common/link_preview.xml`, `<t t-name="mail.LinkPreview.aside" xml:space="preserve">
        <div class="position-absolute top-0 end-0 small">
            <button t-attf-class="{{ className }}" class="o-mail-LinkPreview-aside btn" aria-label="Remove" t-on-click="onClick">
                <i class="fa fa-times"/>
            </button>
        </div>
    </t>
`);
registerTemplate("mail.LinkPreviewConfirmDelete", `/mail/static/src/core/common/link_preview_confirm_delete.xml`, `<t t-name="mail.LinkPreviewConfirmDelete" xml:space="preserve">
        <Dialog size="'md'" title.translate="Confirmation" modalRef="modalRef">
            <p class="mx-3 mb-3">Do you really want to delete this preview?</p>
            <t t-component="props.LinkPreview" linkPreview="props.linkPreview" deletable="false"/>
            <t t-set-slot="footer">
                <button class="btn btn-danger me-2" t-on-click="onClickOk">Delete</button>
                <button t-if="message.linkPreviews.length &gt; 1" class="btn btn-outline-danger me-2" t-on-click="onClickDeleteAll">Delete all previews</button>
                <button class="btn btn-secondary me-2" t-on-click="onClickCancel">Cancel</button>
            </t>
        </Dialog>
    </t>
`);
registerTemplate("mail.LinkPreviewList", `/mail/static/src/core/common/link_preview_list.xml`, `<t t-name="mail.LinkPreviewList" xml:space="preserve">
        <div class="o-mail-LinkPreviewList d-flex flex-column mt-2" t-att-class="{ 'me-2 pe-4': env.inChatWindow and !env.alignedRight, 'ms-2 ps-4': env.inChatWindow and env.alignedRight }">
            <div class="d-flex flex-grow-1 flex-wrap">
                <t t-foreach="props.linkPreviews" t-as="linkPreview" t-key="linkPreview.id">
                   <LinkPreview linkPreview="linkPreview" deletable="props.deletable"/>
                </t>
            </div>
        </div>
    </t>
`);
registerTemplate("mail.MailAttachmentDropzone", `/mail/static/src/core/common/mail_attachment_dropzone.xml`, `<t t-name="mail.MailAttachmentDropzone" xml:space="preserve">
        <Dropzone t-props="this.props">
            <h4>Drop Files here <i class="fa fa-paperclip"/></h4>
        </Dropzone>
    </t>
`);
registerTemplate("mail.Message", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message" xml:space="preserve">
        <div t-if="message.isNotification" class="o-mail-NotificationMessage text-break mx-auto text-muted opacity-75 px-3 text-center smaller" t-on-click="onClickNotificationMessage" t-att-class="props.className" t-ref="root">
            <i t-if="message.notificationIcon" t-attf-class="{{ message.notificationIcon }} me-1"/>
            <span class="o-mail-NotificationMessage-author d-inline" t-if="authorName and !message.body.includes(escape(authorName))" t-esc="authorName"/> <t t-out="message.body"/>
        </div>
        <ActionSwiper t-else="" onRightSwipe="hasTouch() and props.thread?.eq(store.inbox) ? { action: () =&gt; this.message.setDone(), bgColor: 'bg-success', icon: 'fa-check-circle' } : undefined">
            <div class="o-mail-Message position-relative rounded-0" t-att-data-persistent="message.persistent" t-att-data-starred="message.starred" t-att-class="attClass" role="group" t-att-aria-label="messageTypeText" t-on-click="onClick" t-on-mouseenter="onMouseenter" t-on-mouseleave="onMouseleave" t-ref="root" t-if="message.exists()">
                <div class="o-mail-Message-core position-relative d-flex flex-shrink-0">
                    <div class="o-mail-Message-sidebar d-flex flex-shrink-0" t-att-class="{ 'justify-content-end': isAlignedRight, 'align-items-start justify-content-start': !isAlignedRight, 'o-inChatWindow': env.inChatWindow }">
                        <div t-if="!props.squashed" class="o-mail-Message-avatarContainer position-relative bg-view" t-att-class="getAvatarContainerAttClass()">
                            <img class="o-mail-Message-avatar w-100 h-100 rounded" t-att-src="authorAvatarUrl" t-att-class="authorAvatarAttClass"/>
                        </div>
                        <t t-elif="message.isPending" t-call="mail.Message.pendingStatus"/>
                        <t t-elif="!message.is_transient">
                            <small t-if="isActive and props.showDates" class="o-mail-Message-date o-xsmaller mt-2 text-center lh-1" t-att-title="message.datetimeShort">
                                <t t-esc="message.dateSimple"/>
                            </small>
                        </t>
                    </div>
                    <div class="w-100 o-min-width-0" t-att-class="{ 'flex-grow-1': message.composer }" t-ref="messageContent">
                        <div t-if="!props.squashed" class="o-mail-Message-header d-flex flex-wrap align-items-baseline lh-1" t-att-class="{ 'mb-1': !message.is_note }" name="header">
                            <span t-if="authorName and shouldDisplayAuthorName" class="o-mail-Message-author small" t-att-class="getAuthorAttClass()">
                                <strong class="me-1" t-esc="authorName"/>
                            </span>
                            <t t-if="!isAlignedRight" t-call="mail.Message.notification"/>
                            <t t-if="isAlignedRight and message.is_note" t-call="mail.Message.actions"/>
                            <small t-if="!message.is_transient" class="o-mail-Message-date o-xsmaller" t-att-title="message.datetimeShort">
                                <t t-if="message.isPending" t-call="mail.Message.pendingStatus"/>
                                <t t-else="" t-out="message.dateSimpleWithDay"/>
                            </small>
                            <small t-if="isPersistentMessageFromAnotherThread" t-on-click.prevent="openRecord" class="ms-1 text-500">
                                <t t-if="message.thread.model !== 'discuss.channel'">
                                    on <a t-if="message.thread.displayName" t-att-href="message.resUrl" t-esc="message.thread.displayName"/><em class="pe-1 text-decoration-line-through" t-else="">Deleted document</em>
                                </t>
                                <t t-else="">
                                    (from <a t-att-href="message.resUrl"><t t-esc="message.thread.prefix"/><t t-esc="message.thread.displayName or message.default_subject"/></a>)
                                </t>
                            </small>
                            <div t-if="props.message.scheduledDatetime" t-att-class="{ 'ms-2': (props.isInChatWindow and isAlignedRight) or (isPersistentMessageFromAnotherThread) }" t-att-title="props.message.scheduledDateSimple">
                                <span class="text-600 cursor-pointer">
                                    <i class="fa fa-calendar-o"/>
                                </span>
                            </div>
                            <t t-if="isAlignedRight" t-call="mail.Message.notification"/>
                            <t t-if="!isAlignedRight and message.is_note" t-call="mail.Message.actions"/>
                        </div>
                        <div class="o-mail-Message-contentContainer position-relative d-flex" t-att-class="{                                    'flex-row-reverse': isAlignedRight,                                    }">
                            <div class="o-mail-Message-content o-min-width-0" t-att-class="{ 'w-100': state.isEditing, 'opacity-50': message.isPending, 'pt-1': message.is_note }">
                                <div class="o-mail-Message-textContent position-relative d-flex" t-att-class="{ 'w-100': state.isEditing }">
                                    <t t-set="showTextVisually" t-value="!message.linkPreviewSquash and (message.hasTextContent or showSubtypeDescription)"/>
                                    <t t-if="message.message_type === 'notification' and message.body" t-call="mail.Message.bodyAsNotification" name="bodyAsNotification"/>
                                    <t t-if="message.message_type !== 'notification' and !message.is_transient and (message.hasTextContent or message.subtype_description or state.isEditing or message.edited or message.parentMessage)">
                                        <LinkPreviewList t-if="!state.isEditing and message.linkPreviewSquash and !message.parentMessage" linkPreviews="message.linkPreviews" deletable="false"/>
                                        <t t-else="">
                                            <div class="position-relative overflow-x-auto overflow-y-hidden d-inline-block text-body" t-att-class="{ 'w-100': state.isEditing }">
                                                <div t-if="message.bubbleColor" class="o-mail-Message-bubble rounded-bottom-3 position-absolute top-0 start-0 w-100 h-100 border" t-att-class="{                                                     'o-blue': message.bubbleColor === 'blue',                                                     'o-green': message.bubbleColor === 'green',                                                     'o-orange': message.bubbleColor === 'orange',                                                     }" t-attf-class="{{ isAlignedRight ? 'rounded-start-3' : 'rounded-end-3' }}"/>
                                                <MessageInReply t-if="message.parentMessage" class="'mx-2 p-1 pb-0 ' + (showTextVisually ? 'mt-1' : 'my-1')" message="message" onClick="props.onParentMessageClick"/>
                                                <div class="position-relative text-break o-mail-Message-body" t-att-class="{                                                             'p-1': message.is_note,                                                             'fs-1': !state.isEditing and !env.inChatter and message.onlyEmojis,                                                             'mb-0': !message.is_note,                                                             'py-2': !message.is_note and !state.isEditing and showTextVisually,                                                             'pt-2 pb-1': !message.is_note and state.isEditing,                                                             'o-note': message.is_note,                                                             'align-self-start rounded-end-3 rounded-bottom-3': !state.isEditing and !message.is_note,                                                             'flex-grow-1': state.isEditing,                                                             }" t-ref="body">
                                                    <Composer t-if="state.isEditing" autofocus="true" composer="message.composer" onDiscardCallback.bind="exitEditMode" onPostCallback.bind="exitEditMode" mode="env.inChatter ? 'extended' : 'compact'" sidebar="false"/>
                                                    <t t-else="">
                                                        <em t-if="message.subject and !message.isSubjectSimilarToThreadName and !message.isSubjectDefault" class="d-block text-muted smaller">Subject: <t t-out="props.messageSearch?.highlight(message.subject) ?? message.subject"/></em>
                                                        <div class="overflow-x-auto" t-if="message.message_type and message.message_type.includes('email')" t-ref="shadowBody"/>
                                                        <t t-elif="message.showTranslation" t-out="message.translationValue"/>
                                                        <div class="o-mail-Message-richBody overflow-x-auto" t-elif="message.body and !message.linkPreviewSquash" t-out="props.messageSearch?.highlight(message.body) ?? message.body"/>
                                                        <p class="fst-italic text-muted small" t-if="message.showTranslation">
                                                            <t t-if="message.translationSource" t-esc="translatedFromText"/>
                                                        </p>
                                                        <p class="fst-italic text-muted small" t-if="message.translationErrors">
                                                            <i class="text-danger fa fa-warning" role="img" aria-label="Translation Failure"/>
                                                            <t t-if="message.translationErrors" t-esc="translationFailureText"/>
                                                        </p>
                                                        <t t-if="showSubtypeDescription" t-out="props.messageSearch?.highlight(message.subtype_description) ?? message.subtype_description"/>
                                                    </t>
                                                </div>
                                                <div class="o-mail-Message-seenContainer position-absolute">
                                                    <MessageSeenIndicator t-if="showSeenIndicator" message="props.message" thread="props.thread"/>
                                                </div>
                                            </div>
                                        </t>
                                    </t>
                                    <t t-if="!message.is_note and message.hasTextContent and !env.inChatWindow" t-call="mail.Message.actions"/>
                                </div>
                                <AttachmentList t-if="message.attachment_ids.length &gt; 0" attachments="message.attachment_ids.map((a) =&gt; a)" unlinkAttachment.bind="onClickAttachmentUnlink" imagesHeight="message.attachment_ids.length === 1 ? 300 : 75" messageSearch="props.messageSearch"/>
                                <LinkPreviewList t-if="!state.isEditing and message.linkPreviewSquash and message.parentMessage" linkPreviews="message.linkPreviews" deletable="false"/>
                                <LinkPreviewList t-if="message.linkPreviews.length &gt; 0 and store.hasLinkPreviewFeature and !message.linkPreviewSquash" linkPreviews="message.linkPreviews" deletable="message.isSelfAuthored or store.self.isAdmin"/>
                            </div>
                            <t t-if="!message.is_note and (!message.hasTextContent or env.inChatWindow)" t-call="mail.Message.actions"/>
                        </div>
                        <MessageReactions message="message" openReactionMenu="openReactionMenu" t-if="message.reactions.length"/>
                    </div>
                </div>
            </div>
        </ActionSwiper>
    </t>

`);
registerTemplate("mail.Message.edited", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message.edited" xml:space="preserve">
    <em class="smaller fw-bold text-500"> (edited)</em>
</t>

`);
registerTemplate("mail.Message.actions", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message.actions" xml:space="preserve">
    <div t-if="props.hasActions and message.hasActions and !state.isEditing" class="o-mail-Message-actions d-print-none" t-att-class="{             'start-0': isAlignedRight,             'mx-1': !isMobileOS,             'mt-1': !message.is_note,             'my-n2': message.is_note,             'invisible': !isActive and !isMobileOS,             'o-expanded': optionsDropdown.isOpen         }">
        <t t-if="isMobileOS and !mobileExpanded" t-call="mail.Message.expandAction"/>
        <t t-else="">
            <t t-set="isReverse" t-value="env.inChatWindow and isAlignedRight"/>
            <div class="d-flex rounded-1 overflow-hidden" t-att-class="{ 'flex-row-reverse': isReverse }">
                <t t-set="quickActions" t-value="messageActions.actions.slice(0, messageActions.actions.length gt quickActionCount ? quickActionCount - 1 : quickActionCount)"/>
                <t t-foreach="quickActions" t-as="action" t-key="action.id">
                    <t t-set="isStart" t-value="(!isReverse and action.isFirst) or (isReverse and action.isLast)"/>
                    <t t-set="isEnd" t-value="(!isReverse and action.isLast) or (isReverse and action.isFirst)"/>
                    <t t-if="action.callComponent" t-component="action.callComponent" t-props="action.props" classNames="{                         'rounded-start-1': isStart,                         'rounded-end-1': isEnd,                     }"/>
                    <button t-else="" class="btn border-0 px-1 py-0 rounded-0" t-att-title="action.title" t-att-name="action.id" t-on-click.stop="action.onClick" t-att-class="{                         'rounded-start-1': isStart,                         'rounded-end-1': isEnd,                     }">
                        <i class="fa-lg" t-att-class="action.icon"/>
                    </button>
                </t>
                <div t-if="messageActions.actions.length gt quickActionCount" class="d-flex rounded-0">
                    <Dropdown state="optionsDropdown" position="props.message.threadAsNewest  ? 'top-start' : 'bottom-start'" menuClass="'d-flex flex-column py-0 o-mail-Message-moreMenu bg-100 border-secondary'">
                        <t t-call="mail.Message.expandAction"/>
                        <t t-set-slot="content">
                            <t t-foreach="messageActions.actions.slice(quickActionCount - 1)" t-as="action" t-key="action.id">
                                <DropdownItem class="'px-2 py-1 d-flex align-items-center rounded-0 ' + action.btnClass" onSelected="action.onClick" attrs="{ title: action.title}">
                                    <t t-if="action.callComponent" t-component="action.callComponent" t-props="action.props"/>
                                    <t t-else="">
                                        <i class="fa-fw" t-att-class="action.icon"/>
                                        <span class="mx-2" t-esc="action.title"/>
                                    </t>
                                </DropdownItem>
                            </t>
                        </t>
                    </Dropdown>
                </div>
                <t t-foreach="Array.from({ length: quickActionCount - quickActions.length - 1 })" t-as="emptyQuickAction" t-key="emptyQuickAction_index">
                    <button class="btn border-0 px-1 py-0 rounded-0 opacity-0 pe-none">
                        <i class="fa-lg fa fa-question"/>
                    </button>
                </t>
            </div>
        </t>
    </div>
</t>


`);
registerTemplate("mail.Message.expandAction", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message.expandAction" xml:space="preserve">
    <button class="btn border-0 rounded-0" t-att-title="expandText" t-on-click="openMobileActions" t-att-class="{         'o-mail-Message-openActionMobile opacity-25 p-2 mt-n2 rounded-circle user-select-none': isMobileOS and !mobileExpanded,         'me-n2': isMobileOS and !mobileExpanded and isAlignedRight,         'ms-n2': isMobileOS and !mobileExpanded and !isAlignedRight,         'px-2 py-0': !isMobileOS,         'rounded-start-1': !isMobileOS and isReverse,         'rounded-end-1': !isMobileOS and !isReverse,     }">
        <i class="fa fa-lg fa-ellipsis-v" t-att-class="{ 'order-1': props.isInChatWindow, 'fa-fw': isMobileOS }" tabindex="1"/>
    </button>
</t>

`);
registerTemplate("mail.Message.notification", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message.notification" xml:space="preserve">
    <div t-if="message.thread?.eq(props.thread) and message.notifications.length &gt; 0" class="mx-1">
        <span class="o-mail-Message-notification cursor-pointer opacity-100-hover" t-att-class="message.failureNotifications.length &gt; 0 ? 'text-danger opacity-75' : 'opacity-50'" role="button" tabindex="0" t-on-click="onClickNotification">
            <i t-att-class="message.notifications[0].icon" role="img" aria-label="Delivery failure"/> <span class="fw-bold small" t-if="message.notifications[0].label" t-out="message.notifications[0].label"/>
        </span>
    </div>
</t>

`);
registerTemplate("mail.Message.bodyAsNotification", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message.bodyAsNotification" xml:space="preserve">
    <div class="o-mail-Message-body text-break mb-0 w-100" t-att-class="{'o-note': message.message_type == 'notification'}">
        <t t-out="props.messageSearch?.highlight(message.body) ?? message.body"/>
    </div>
</t>

`);
registerTemplate("mail.Message.pendingStatus", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message.pendingStatus" xml:space="preserve">
    <button t-if="message.postFailRedo" class="btn p-0" title="Failed to post the message. Click to retry" t-on-click="() =&gt; message.postFailRedo?.()"><i class="fa fa-fw fa-warning text-warning"/></button>
    <span t-else="" class="o-mail-Message-pendingProgress"><i class="fa fa-fw fa-circle-o-notch fa-spin opacity-50"/></span>
</t>

`);
registerTemplate("mail.Message.mentionedChannelIcon", `/mail/static/src/core/common/message.xml`, `<t t-name="mail.Message.mentionedChannelIcon" xml:space="preserve">
    <i t-att-class="icon"/>
</t>

`);
registerTemplate("mail.MessageActionMenuMobile", `/mail/static/src/core/common/message_action_menu_mobile.xml`, `<t t-name="mail.MessageActionMenuMobile" xml:space="preserve">
        <Dialog size="'lg'" header="false" footer="false" contentClass="'o-discuss-mobileContextMenu d-flex position-absolute bottom-0 rounded-0 h-50 bg-100'" modalRef="modalRef" withBodyPadding="false">
            <div class="btn-group d-flex flex-column rounded-3 gap-1 p-3" t-on-click.stop="">
                <t t-foreach="messageActions.actions.slice(quickActionCount)" t-as="action" t-key="action.id">
                    <button class="btn px-3 py-3 d-flex align-items-center rounded-0 btn-group-item gap-3 user-select-none bg-200" t-att-class="{ 'rounded-top-3': action_first, 'rounded-bottom-3': action_last }" t-on-click="() =&gt; this.onClickAction(action)">
                        <i class="fa-lg fa-fw fs-2" t-att-class="action.icon"/>
                        <span class="fs-4" t-esc="action.title"/>
                    </button>
                </t>
            </div>
        </Dialog>
    </t>
`);
registerTemplate("mail.MessageCardList", `/mail/static/src/core/common/message_card_list.xml`, `<t t-name="mail.MessageCardList" xml:space="preserve">
        <div class="o-mail-MessageCardList d-flex flex-column" t-att-class="{ 'justify-content-center flex-grow-1': props.messages.length === 0 }" t-ref="message-list">
            <div class="card mb-2" t-foreach="props.messages" t-as="message" t-key="message.id">
                <div class="card-body py-2">
                    <div class="position-absolute top-0 end-0 z-1 mx-2 my-1">
                        <a role="button" class="o-mail-MessageCard-jump rounded bg-400 badge opacity-0" t-att-class="{ 'opacity-100 py-1 px-2': ui.isSmall }" t-on-click="() =&gt; this.onClickJump(message)">Jump</a>
                        <button t-if="props.mode === 'pin' and store.self.type === 'partner'" class="btn btn-link text-reset ms-2 p-0" t-att-class="{ 'fs-5': ui.isSmall }" title="Unpin" t-on-click="message.unpin">
                            <i class="oi oi-close"/>
                        </button>
                    </div>
                    <Message hasActions="false" message="message" thread="props.thread" messageSearch="props.messageSearch"/>
                </div>
            </div>
            <span t-if="props.loadMore" t-ref="load-more"/>
            <p t-if="props.showEmpty !== undefined ? props.showEmpty : props.messages.length === 0" t-esc="emptyText" class="text-center fst-italic text-500 fs-6"/>
        </div>
    </t>

`);
registerTemplate("mail.MessageConfirmDialog", `/mail/static/src/core/common/message_confirm_dialog.xml`, `<t t-name="mail.MessageConfirmDialog" xml:space="preserve">
    <Dialog size="props.size" title="props.title">
        <p class="mx-3 mb-3" t-esc="props.prompt"/>
        <blockquote class="mx-3 mb-3 fst-normal" style="pointer-events:none;">
            <t t-component="messageComponent" message="props.message" hasActions="false"/>
        </blockquote>
        <t t-set-slot="footer">
            <button class="btn me-2" t-att-class="props.confirmColor" t-on-click="onClickConfirm" t-out="props.confirmText"/>
            <button class="btn btn-secondary me-2" t-on-click="props.close">Cancel</button>
        </t>
    </Dialog>
</t>

`);
registerTemplate("mail.MessageInReply", `/mail/static/src/core/common/message_in_reply.xml`, `<t t-name="mail.MessageInReply" xml:space="preserve">
        <div class="o-mail-MessageInReply" t-attf-class="{{ props.class }}">
            <small class="o-mail-MessageInReply-core o-mail-Message-bubble o-muted border position-relative d-flex px-2 py-1 rounded-3 rounded-start-0 d-inline-flex" t-att-class="{                 'o-blue': props.message.parentMessage.bubbleColor === 'blue',                 'o-green': props.message.parentMessage.bubbleColor === 'green',                 'o-orange': props.message.parentMessage.bubbleColor === 'orange',             }">
                <span t-if="!props.message.parentMessage.isEmpty" class="d-inline-flex align-items-center text-muted opacity-75" t-att-class="{ 'cursor-pointer opacity-100-hover': props.onClick }" t-on-click="() =&gt; this.props.onClick?.()">
                    <img class="o-mail-MessageInReply-avatar me-2 rounded o_object_fit_cover" t-att-src="authorAvatarUrl" t-att-title="props.message.parentMessage.author?.name ?? props.message.parentMessage.email_from" alt="Avatar"/>
                    <span class="o-mail-MessageInReply-content overflow-hidden smaller">
                        <b><t t-out="props.message.parentMessage.author?.name ?? props.message.parentMessage.email_from"/></b>:
                        <span class="o-mail-MessageInReply-message ms-1 text-break">
                            <t t-if="!props.message.parentMessage.isBodyEmpty">
                                <t t-out="props.message.parentMessage.body"/>
                                <em t-if="props.message.parentMessage.edited" class="smaller fw-bold text-500"> (edited)</em>
                            </t>
                            <t t-elif="props.message.parentMessage.attachment_ids.length &gt; 0">
                                <span class="me-2 fst-italic">Click to see the attachments</span>
                                <i class="fa fa-image"/>
                            </t>
                        </span>
                    </span>
                </span>
                <i t-else="" class="text-muted ms-2">Original message was deleted</i>
            </small>
        </div>
    </t>
`);
registerTemplate("mail.MessageNotificationPopover", `/mail/static/src/core/common/message_notification_popover.xml`, `<t t-name="mail.MessageNotificationPopover" xml:space="preserve">
        <div class="o-mail-MessageNotificationPopover m-2">
            <div t-foreach="props.message.notifications" t-as="notification" t-key="notification.id">
                <i class="me-2" t-att-class="notification.statusIcon" t-att-title="notification.statusTitle" role="img"/>
                <span t-if="notification.persona" t-esc="notification.persona.name"/>
            </div>
        </div>
    </t>

`);
registerTemplate("mail.MessageReactionButton", `/mail/static/src/core/common/message_reaction_button.xml`, `<t t-name="mail.MessageReactionButton" xml:space="preserve">
        <button class="btn px-1 py-0 lh-1 rounded-0 border-0" t-att-class="props.classNames" tabindex="1" t-att-title="props.action.title" aria-label="props.action.title" t-ref="emoji-picker"><i class="fa-lg" t-att-class="props.action.icon"/></button>
    </t>

`);
registerTemplate("mail.MessageReactionList", `/mail/static/src/core/common/message_reaction_list.xml`, `<t t-name="mail.MessageReactionList" xml:space="preserve">
        <Dropdown state="preview" position="'top-middle'" menuClass="'bg-view border-0 p-0 mt-1 overflow-visible shadow-sm'" manual="true">
            <t t-call="mail.MessageReactionList.button"/>
            <t t-set-slot="content">
                <t t-call="mail.MessageReactionList.preview"/>
            </t>
        </Dropdown>
    </t>

    `);
registerTemplate("mail.MessageReactionList.button", `/mail/static/src/core/common/message_reaction_list.xml`, `<t t-name="mail.MessageReactionList.button" xml:space="preserve">
        <button class="position-relative o-mail-MessageReaction btn d-flex p-0 border rounded-2 mb-1 fs-5 px-1 gap-1 align-items-center" t-on-click="() =&gt; this.onClickReaction(props.reaction)" t-on-contextmenu="onContextMenu" t-on-mouseenter="loadEmoji" t-ref="reactionButton" t-att-class="{             'o-selfReacted border-primary text-primary fw-bold': hasSelfReacted(props.reaction),             'bg-view border-secondary': !hasSelfReacted(props.reaction),             'ms-1': env.inChatWindow and env.alignedRight,             'me-1': !(env.inChatWindow and env.alignedRight),             'cursor-default': !props.message.canAddReaction(),         }">
            <span t-esc="props.reaction.content"/>
            <span class="small" t-esc="props.reaction.count"/>
        </button>
    </t>

    `);
registerTemplate("mail.MessageReactionList.preview", `/mail/static/src/core/common/message_reaction_list.xml`, `<t t-name="mail.MessageReactionList.preview" xml:space="preserve">
        <div class="o-mail-MessageReactionList-preview px-0 py-1 border cursor-pointer d-flex" t-on-click="(ev) =&gt; this.onClickReactionList(props.reaction)" t-ref="reactionList">
            <div class="d-flex align-items-center mx-2 gap-2">
                <span class="fs-1" t-esc="props.reaction.content"/>
                <span class="o-mail-MessageReactionList-previewText small me-1" t-esc="previewText(props.reaction)"/>
            </div>
        </div>
    </t>

`);
registerTemplate("mail.MessageReactionMenu", `/mail/static/src/core/common/message_reaction_menu.xml`, `<t t-name="mail.MessageReactionMenu" xml:space="preserve">
        <Dialog size="'md'" header="false" footer="false" contentClass="'o-mail-MessageReactionMenu h-50 d-flex'">
            <div class="d-flex h-100" t-on-keydown="onKeydown" t-ref="root">
                <div class="d-flex overflow-auto flex-column bg-100 p-2 h-100 border-end">
                    <t t-foreach="props.message.reactions" t-as="reaction" t-key="reaction.content">
                        <button class="btn p-1 rounded-2 mx-2 py-0 d-flex align-items-center" t-att-class="{ 'bg-200 border-primary': reaction.eq(state.reaction) }" t-att-title="getEmojiShortcode(reaction)" t-on-click="() =&gt; state.reaction = reaction">
                            <span class="mx-1 fs-2" t-esc="reaction.content"/>
                            <span class="mx-1 pe-2" t-att-class="{ 'text-primary': reaction.eq(state.reaction) }" t-esc="reaction.count"/>
                        </button>
                    </t>
                </div>
                <div class="d-flex overflow-auto flex-column flex-grow-1 bg-view p-2 h-100">
                    <div t-foreach="state.reaction.personas" t-as="persona" t-key="persona.id" class="o-mail-MessageReactionMenu-persona d-flex p-1 align-items-center" t-att-class="{ 'o-isDeviceSmall': ui.isSmall }">
                        <img class="rounded o_object_fit_cover o-mail-MessageReactionMenu-avatar" t-att-src="persona.avatarUrl"/>
                        <span class="d-flex flex-grow-1 align-items-center">
                            <span class="mx-2 text-truncate fs-6" t-esc="persona.name"/>
                            <div class="flex-grow-1"/>
                            <button t-if="props.message.effectiveSelf.eq(persona)" class="btn btn-light fa fa-trash rounded-pill bg-inherit border-0" title="Remove" t-on-click.stop="() =&gt; state.reaction.remove()"/>
                        </span>
                    </div>
                </div>
            </div>
        </Dialog>
    </t>

`);
registerTemplate("mail.MessageReactions", `/mail/static/src/core/common/message_reactions.xml`, `<t t-name="mail.MessageReactions" xml:space="preserve">
    <div class="o-mail-MessageReactions position-relative d-flex flex-wrap mt-n1" t-att-class="{             'flex-row-reverse me-3': env.inChatWindow and env.alignedRight,             'ms-3': !(env.inChatWindow and env.alignedRight) and (props.message.is_discussion),             'o-emojiPickerOpen': emojiPicker.isOpen,         }">
        <t t-foreach="props.message.reactions" t-as="reaction" t-key="reaction.content">
            <MessageReactionList message="this.props.message" openReactionMenu="this.props.openReactionMenu" reaction="reaction"/>
        </t>
        <button t-if="props.message.canAddReaction()" class="o-mail-MessageReactions-add btn bg-inherit d-flex px-1 py-0 border-0 rounded-0 mb-1 align-items-center fs-5 opacity-75 opacity-100-hover" title="Add Reaction" t-ref="add"><i class="oi fa-fw oi-smile-add"/></button>
    </div>
</t>
`);
registerTemplate("mail.MessageSeenIndicator", `/mail/static/src/core/common/message_seen_indicator.xml`, `<t t-name="mail.MessageSeenIndicator" xml:space="preserve">
        <span class="o-mail-MessageSeenIndicator position-relative" t-att-class="{ 'opacity-50': !props.message.hasEveryoneSeen, 'text-primary opacity-75': props.message.hasEveryoneSeen, 'cursor-pointer': props.message.channelMemberHaveSeen.length }" t-att-title="summary" t-attf-class="{{ props.className }}" t-on-click="openDialog">
            <t t-if="!props.message.isMessagePreviousToLastSelfMessageSeenByEveryone">
                <i t-if="props.message.hasSomeoneFetched or props.message.hasSomeoneSeen" class="fa fa-check ps-1"/>
                <i t-if="props.message.hasSomeoneSeen" class="o-second start-0 fa fa-check position-absolute"/>
            </t>
        </span>
    </t>

    `);
registerTemplate("mail.MessageSeenIndicatorPopover.card", `/mail/static/src/core/common/message_seen_indicator.xml`, `<t t-name="mail.MessageSeenIndicatorPopover.card" xml:space="preserve">
        <div class="o-mail-MessageSeenIndicatorPopover-card d-flex align-items-center gap-2">
            <span class="o_avatar position-relative o_card_avatar" style="width: 30px;height:30px;">
                <img t-att-src="member.persona.avatarUrl" class="w-100 h-100 rounded o_object_fit_cover"/>
            </span>
            <span class="fw-bold" t-esc="member.persona.name"/>
        </div>
    </t>

    `);
registerTemplate("mail.MessageSeenIndicatorDialog", `/mail/static/src/core/common/message_seen_indicator.xml`, `<t t-name="mail.MessageSeenIndicatorDialog" xml:space="preserve">
        <Dialog size="'sm'" title.translate="Seen by:" footer="false" withBodyPadding="false">
            <ul class="list-group list-group-flush list-unstyled py-1" t-ref="content">
                <li class="list-group-item py-2" t-foreach="props.message.channelMemberHaveSeen" t-as="member" t-key="member.id">
                    <t t-call="mail.MessageSeenIndicatorPopover.card"/>
                </li>
            </ul>
        </Dialog>
    </t>

`);
registerTemplate("mail.NavigableList", `/mail/static/src/core/common/navigable_list.xml`, `<t t-name="mail.NavigableList" xml:space="preserve">
        <div class="o-mail-NavigableList bg-view m-0 p-0 shadow-sm" t-ref="root" t-att-class="props.class">
            <div t-if="show" class="o-open border d-flex flex-column bg-inherit" t-on-mousedown.prevent="">
                <div t-if="state.showLoading" t-att-class="{ 'position-absolute bg-inherit smaller o-mail-NavigableList-floatingLoading': props.options.length, 'bg-300': props.options.length and state.activeIndex === 0, 'o-mail-NavigableList-item': !props.options.length }">
                    <t t-call="mail.NavigableList.spinner"/>
                </div>
                <div t-foreach="sortedOptions" t-as="option" t-key="option_index" class="o-mail-NavigableList-item" t-att-class="option.classList" t-on-mouseenter="() =&gt; this.onOptionMouseEnter(option_index)" t-on-click="(ev) =&gt; this.selectOption(ev, option_index)">
                    <hr class="my-2" t-if="option.group != lastGroup and option_index != 0"/>
                    <a role="button" class="d-flex align-items-center w-100 px-3 py-1 small" t-att-class="{ 'o-mail-NavigableList-active': state.activeIndex === option_index }">
                        <t t-if="option.optionTemplate" t-call="{{ option.optionTemplate }}"/>
                        <t t-elif="props.optionTemplate" t-call="{{ props.optionTemplate }}"/>
                        <t t-else="" t-esc="option.label"/>
                    </a>
                    <t t-set="lastGroup" t-value="option.group"/>
                </div>
                <span t-if="props.hint" class="text-muted fst-italic form-text align-self-end m-0 me-1 smaller" t-esc="props.hint"/>
            </div>
        </div>
    </t>

    `);
registerTemplate("mail.NavigableList.spinner", `/mail/static/src/core/common/navigable_list.xml`, `<t t-name="mail.NavigableList.spinner" xml:space="preserve">
        <span class="d-flex align-items-center w-100 py-2 gap-1 text-muted opacity-75" t-att-class="{ 'px-1': props.options.length, 'px-4': !props.options.length }">
            <i class="fa fa-spin fa-circle-o-notch"/>
            Loading…
        </span>
    </t>
`);
registerTemplate("mail.Picker", `/mail/static/src/core/common/picker.xml`, `<t t-name="mail.Picker" xml:space="preserve">
    <div t-if="ui.isSmall" class="o-mail-Picker d-flex flex-column w-100" t-att-class="{'d-none': props.state.picker === props.PICKERS.NONE}">
        <PickerContent t-props="contentProps"/>
    </div>
</t>

`);
registerTemplate("mail.PickerContent", `/mail/static/src/core/common/picker_content.xml`, `<t t-name="mail.PickerContent" xml:space="preserve">
    <div class="o-mail-PickerContent d-flex flex-column flex-grow-1 o-min-height-0 bg-100 rounded-2" t-on-click="onClick">
        <div class="o-mail-PickerContent-picker d-flex flex-grow-1 rounded overflow-auto">
            <div t-if="props.state.picker === props.PICKERS.EMOJI" class="o-mail-PickerContent-emojiPicker d-flex flex-grow-1 mw-100">
                <EmojiPicker close="props.close" onSelect="props.pickers.emoji" state="props.state" storeScroll="props.storeScroll"/>
            </div>
        </div>
    </div>
</t>

`);
registerTemplate("mail.SearchMessageInput", `/mail/static/src/core/common/search_message_input.xml`, `<t t-name="mail.SearchMessageInput" xml:space="preserve">
        <div class="o-mail-SearchMessageInput d-flex py-2">
            <div class="input-group">
                <div class="o_searchview form-control d-flex align-items-center bg-view p-0" role="search" aria-autocomplete="list">
                    <div class="o_searchview_input_container d-flex flex-grow-1 flex-wrap gap-1 h-100">
                        <input type="text" class="o_searchview_input flex-grow-1 w-auto border-0 rounded-start px-2" accesskey="Q" placeholder="Search" t-model="state.searchTerm" t-on-keydown="onKeydownSearch" t-ref="autofocus" role="searchbox"/>
                    </div>
                </div>
                <button class="btn" t-att-class="state.searchedTerm === state.searchTerm ? 'btn-outline-secondary' : 'btn-secondary'" t-on-click="() =&gt; this.search()" aria-label="Search button">
                    <i t-if="!props.messageSearch.searching" class="o_searchview_icon oi oi-search" role="img" aria-label="Search Messages" title="Search Messages"/>
                    <i t-else="" class="fa fa-spin fa-spinner" aria-label="Search in progress" title="Search in progress"/>
                </button>
            </div>
            <button t-if="env.inChatter" class="btn btn-outline-secondary ms-3" t-on-click="() =&gt; this.clear()" aria-label="Close button">
                <i class="o_searchview_icon oi oi-close cursor-pointer" role="img" aria-label="Close search" title="Close"/>
            </button>
        </div>
    </t>
`);
registerTemplate("mail.SearchMessageResult", `/mail/static/src/core/common/search_message_result.xml`, `<t t-name="mail.SearchMessageResult" xml:space="preserve">
        <div class="o-mail-SearchMessageResult">
            <p t-if="MESSAGE_FOUND" class="o-mail-SearchMessagesPanel-title py-1 mb-0 fw-bolder text-center text-uppercase text-muted">
                <t t-out="MESSAGE_FOUND"/>
            </p>
            <MessageCardList messages="props.messageSearch.messages" thread="props.thread" mode="'search'" messageSearch="props.messageSearch" showEmpty="props.messageSearch.messages.length === 0 and props.messageSearch.searched" onClickJump="() =&gt; this.props.onClickJump?.()" loadMore="props.messageSearch.loadMore" onLoadMoreVisible.bind="onLoadMoreVisible"/>    
        </div>
    </t>
`);
registerTemplate("mail.SearchMessagesPanel", `/mail/static/src/core/common/search_messages_panel.xml`, `<t t-name="mail.SearchMessagesPanel" xml:space="preserve">
        <ActionPanel title="title" minWidth="200" initialWidth="400" icon="'oi oi-search'">
            <SearchMessageInput closeSearch="props.closeSearch" messageSearch="messageSearch" thread="props.thread"/>
            <SearchMessageResult thread="props.thread" messageSearch="messageSearch"/>
        </ActionPanel>
    </t>
`);
registerTemplate("mail.Thread", `/mail/static/src/core/common/thread.xml`, `<t t-name="mail.Thread" xml:space="preserve">
    <t t-if="!env.inChatter" t-call="mail.Thread.jumpUnread"/>
    <div class="o-mail-Thread position-relative flex-grow-1 d-flex flex-column overflow-auto" t-att-class="{ 'pb-5': env.inChatter?.aside, 'pb-4': !env.inChatter?.aside, 'px-3': !env.inChatter and !props.isInChatWindow }" t-ref="messages" tabindex="-1">
        <t t-if="!props.thread.isEmpty or props.thread.loadOlder or props.thread.hasLoadingFailed" name="content">
            <div class="d-flex flex-column position-relative flex-grow-1" t-att-class="{'justify-content-end': !env.inChatter and props.thread.model !== 'mail.box'}">
                <span class="position-absolute w-100 invisible" t-att-class="props.order === 'asc' ? 'bottom-0' : 'top-0'" t-ref="present-treshold" t-att-style="\`height: Min(\${PRESENT_THRESHOLD}px, 100%)\`"/>
                <t t-set="currentDay" t-value="0"/>
                <t t-if="props.order === 'asc'">
                    <t t-if="props.thread.loadOlder and !props.thread.isTransient and !props.thread.hasLoadingFailed" t-call="mail.Thread.loadOlder"/>
                    <t t-if="props.thread.hasLoadingFailed" t-call="mail.Thread.loadingError"/>
                </t>
                <span t-else="" class="pt-1" t-ref="load-newer"/>
                <t t-set="messages" t-value="orderedMessages"/>
                <t t-if="state.mountedAndLoaded" t-foreach="messages" t-as="msg" t-key="msg.id">
                    <t t-set="prevMsg" t-value="messages[msg_index -1]"/>
                    <t t-if="msg.dateDay !== currentDay and props.showDates">
                        <DateSection date="msg.dateDay" className="'pt-2 px-2'"/>
                        <t t-set="currentDay" t-value="msg.dateDay"/>
                    </t>
                    <div t-if="msg.threadAsFirstUnread?.eq(props.thread)" class="o-mail-Thread-newMessage d-flex align-items-center fw-bolder z-1 px-2">
                        <hr class="flex-grow-1 border-danger opacity-100"/><span class="ps-2 pe-1 bg-danger o-text-white rounded text-uppercase">New</span>
                    </div>
                    <Message asCard="props.thread.model === 'mail.box'" className="getMessageClassName(msg)" isInChatWindow="props.isInChatWindow" message="msg" previousMessage="prevMsg" registerMessageRef="registerMessageRef" messageToReplyTo="props.messageToReplyTo" squashed="isSquashed(msg, prevMsg)" onParentMessageClick.bind="() =&gt; this.onParentMessageClick(msg.parentMessage)" thread="props.thread" messageEdition="props.messageEdition" isFirstMessage="msg_first" hasActions="props.messageActions and !msg.eq(props.thread.from_message_id)" showDates="props.showDates"/>
                </t>
                <span t-if="props.order === 'asc'" class="pt-1" t-ref="load-newer"/>
                <t t-else="">
                    <t t-if="props.thread.loadOlder and !props.thread.isTransient and !props.thread.hasLoadingFailed" t-call="mail.Thread.loadOlder"/>
                    <t t-if="props.thread.hasLoadingFailed" t-call="mail.Thread.loadingError"/>
                </t>
            </div>
        </t>
        <t t-else="">
            <div class="o-mail-Thread-empty d-flex flex-column align-items-center justify-content-center text-muted h-100 opacity-75" t-att-class="{'p-4': props.showEmptyMessage}">
                <t t-if="props.thread.isLoaded and props.showEmptyMessage">
                    <t name="empty-message">
                        <span class="fs-1" style="filter: grayscale(1);">😶</span>
                        <span>The conversation is empty.</span>
                    </t>
                </t>
            </div>
        </t>
        <t t-call="mail.Thread.jumpPresent"/>
    </div>
</t>

`);
registerTemplate("mail.Thread.jumpPresent", `/mail/static/src/core/common/thread.xml`, `<t t-name="mail.Thread.jumpPresent" xml:space="preserve">
    <button t-if="props.showJumpPresent and state.showJumpPresent" class="o-mail-Thread-jumpPresent position-fixed p-2 rounded-circle lh-1 m-n3 user-select-none btn btn-light shadow-sm border border-secondary" t-att-class="{ 'bottom-0': env.inChatter and !env.inChatter.aside }" t-ref="jump-present" t-on-click="() =&gt; this.jumpToPresent()" title="Jump to Present"><i class="oi text-muted" t-att-class="{ 'oi-chevron-down': props.order === 'asc', 'oi-chevron-up': props.order !== 'asc' }"/></button>
</t>

`);
registerTemplate("mail.Thread.jumpUnread", `/mail/static/src/core/common/thread.xml`, `<t t-name="mail.Thread.jumpUnread" xml:space="preserve">
    <span t-if="props.thread.showUnreadBanner" class="o-mail-Thread-banner d-flex cursor-pointer border-bottom border-warning smaller fw-bolder">
        <t t-set="alertClass" t-value="'alert alert-warning m-0 border-start-0 o-mail-Thread-bannerHover rounded-0 px-2 py-1 smaller'"/>
        <span t-attf-class="{{ alertClass }} flex-grow-1" t-on-click="onClickUnreadMessagesBanner" t-esc="newMessageBannerText"/>
        <span t-attf-class="{{ alertClass }}" t-on-click="() =&gt; props.thread.markAsRead({ sync: true })">Mark as Read<i class="ms-2 fa fa-check-square"/></span>
    </span>
</t>
`);
registerTemplate("mail.Thread.loadOlder", `/mail/static/src/core/common/thread.xml`, `<t t-name="mail.Thread.loadOlder" xml:space="preserve">
    <button class="btn btn-link" t-on-click="onClickLoadOlder" t-ref="load-older">Load More</button>
</t>

`);
registerTemplate("mail.Thread.loadingError", `/mail/static/src/core/common/thread.xml`, `<t t-name="mail.Thread.loadingError" xml:space="preserve">
    <div class="d-flex flex-grow-1 align-items-center justify-content-center flex-column">
        <div class="o-mail-Thread-error">
            An error occurred while fetching messages.
        </div>
        <span t-if="props.thread.hasLoadingFailedError" class="o-xsmaller text-muted" t-esc="props.thread.hasLoadingFailedError"/>
        <button class="btn btn-link" t-on-click="onClickLoadOlder">
            Click here to retry
        </button>
    </div>
</t>
`);
registerTemplate("mail.ThreadIcon", `/mail/static/src/core/common/thread_icon.xml`, `<t t-name="mail.ThreadIcon" xml:space="preserve">
        <div class="o-mail-ThreadIcon d-flex justify-content-center flex-shrink-0" t-att-class="props.className">
            <t t-set="largeClass" t-value="props.size === 'large' ? 'fa-lg' : ''"/>
            <t t-if="props.thread.channel_type === 'channel'">
                <div t-if="props.thread.authorizedGroupFullName" class="fa fa-fw fa-hashtag" t-att-class="largeClass" t-att-title="props.thread.accessRestrictedToGroupText"/>
                <div t-if="!props.thread.authorizedGroupFullName" class="fa fa-fw fa-globe" t-att-class="largeClass" title="Public Channel"/>
            </t>
            <t t-elif="props.thread.channel_type?.includes('chat') and correspondent">
                <t name="chat">
                    <t name="chat_static">
                        <div t-if="correspondent.persona.im_status === 'online'" class="fa fa-fw fa-circle text-success" title="Online"/>
                        <div t-elif="correspondent.persona.im_status === 'offline'" class="fa fa-fw fa-circle-o opacity-75" title="Offline"/>
                        <div t-elif="correspondent.persona.im_status === 'away'" class="fa fa-fw fa-circle o-yellow" title="Away"/>
                        <div t-elif="correspondent.persona.im_status === 'bot'" class="fa fa-fw fa-heart text-success" title="Bot"/>
                        <div t-else="" class="fa-fw" t-att-class="largeClass" t-attf-class="#{ defaultChatIcon.class }" t-att-title="defaultChatIcon.title"/>
                    </t>
                </t>
            </t>
            <div t-elif="props.thread.channel_type === 'group'" class="o-mail-ThreadIcon fa fa-fw fa-users" t-att-class="largeClass" title="Grouped Chat"/>
            <t t-elif="props.thread.model === 'mail.box'">
                <div t-if="props.thread.id === 'inbox'" class="fa fa-fw fa-inbox" t-att-class="largeClass" title="Inbox"/>
                <div t-elif="props.thread.id === 'starred'" class="fa fa-fw fa-star-o" t-att-class="largeClass" title="Favorites"/>
                <div t-elif="props.thread.id === 'history'" class="fa fa-fw fa-history" t-att-class="largeClass" title="History"/>
            </t>
        </div>
    </t>

`);
registerTemplateExtension("mail.Composer.fullComposer", `/portal/static/src/chatter/core/composer_patch.xml`, `<t t-inherit="mail.Composer.fullComposer" t-inherit-mode="extension" xml:space="preserve">
        <xpath expr="//button[@title='Full composer']" position="replace"/>
    </t>
`);
registerTemplateExtension("mail.Message", `/portal/static/src/chatter/core/message_patch.xml`, `<t t-inherit="mail.Message" t-inherit-mode="extension" xml:space="preserve">
        <xpath expr="//t[@t-call='mail.Message.notification']" position="replace"/>
    </t>
`);
registerTemplate("portal.Chatter", `/portal/static/src/chatter/core/portal_chatter.xml`, `<t t-name="portal.Chatter" xml:space="preserve">
    <div t-if="state.thread" class="o-mail-Chatter o-portal-Chatter w-100 h-100 flex-grow-1 d-flex pt-2" t-att-class="{ 'row':props.twoColumns, 'flex-column':!props.twoColumns }" t-on-scroll="onScrollDebounced" t-ref="root">
        <div class="o-mail-Chatter-top top-0" t-att-class="{ 'col-lg-5':props.twoColumns, 'position-sticky':props.composer }" t-att-style="(!props.twoColumns and env.inFrontendPortalChatter and !env.projectSharingId) and 'top: -1px !important; margin-top:-15px; padding-top: 20px'" t-ref="top">
            <Composer t-if="props.composer" composer="state.thread.composer" mode="'extended'" onPostCallback.bind="onPostCallback" dropzoneRef="rootRef" t-key="props.threadId" type="'message'"/>
        </div>
        <div class="o-mail-Chatter-content" t-att-class="{ 'col-lg-6 offset-lg-1':props.twoColumns }">
            <Thread thread="state.thread" t-key="state.thread.localId" order="'desc'" scrollRef="rootRef" jumpPresent="state.jumpThreadPresent"/>
        </div>
    </div>
</t>

`);
registerTemplateExtension("mail.Composer", `/portal_rating/static/src/chatter/frontend/composer_patch.xml`, `<t t-inherit="mail.Composer" t-inherit-mode="extension" xml:space="preserve">
        <xpath expr="//div[hasclass('o-mail-Composer-coreMain')]" position="before">
            <div t-if="env.displayRating and !message" class="o-mail-Composer-starCard d-flex">
                <div class="o-mail-Composer-stars enabled" t-on-mouseleave="onMouseLeaveStar">
                    <t t-set="index" t-value="0"/>
                    <t t-foreach="Array(visibleRatingValue)" t-as="num" t-key="num_index">
                        <i class="fa fa-star" role="img" aria-label="Full star" t-att-index="index" t-on-mousemove="onMoveStar" t-on-click="onClickStar"/>
                        <t t-set="index" t-value="index + 1"/>
                    </t>
                    <t t-foreach="Array(MAX_STAR_RATING - visibleRatingValue)" t-as="num" t-key="Array(visibleRatingValue) + num_index">
                        <i class="fa fa-star-o text-black-25" role="img" aria-label="Empty star" t-att-index="index" t-on-mousemove="onMoveStar" t-on-click="onClickStar"/>
                        <t t-set="index" t-value="index + 1"/>
                    </t>
                </div>
            </div>
        </xpath>
    </t>
`);
registerTemplateExtension("mail.Message", `/portal_rating/static/src/chatter/frontend/message_patch.xml`, `<t t-inherit="mail.Message" t-inherit-mode="extension" xml:space="preserve">
        <xpath expr="//div[@name='header']" position="after">
            <div t-if="ratingValue" class="mb-2">
                <t t-call="portal_rating.rating_stars_static">
                    <t t-set="val" t-value="ratingValue"/>
                </t>
            </div>
        </xpath>
        <xpath expr="//*[@t-ref='messageContent']" position="inside">

            <div t-if="message.rating and message.rating.id" class="o_wrating_publisher_container">
                <button t-if="store.self.is_user_publisher" t-attf-class="btn px-2 my-2 btn-sm border o_wrating_js_publisher_comment_btn {{ message.rating.publisher_comment !== '' ? 'd-none' : '' }}" t-att-data-mes_index="message.rating.mes_index" t-on-click="onClikEditComment">
                    <i class="fa fa-comment text-muted me-1"/>Comment
                </button>
                <div t-if="state.editRating" class="mt-2">
                    <Composer autofocus="true" composer="message.composer" onDiscardCallback.bind="exitEditCommentMode" onPostCallback.bind="exitEditCommentMode" mode="'compact'"/>
                </div>
                <div t-if="message.rating.publisher_comment and !state.editRating" class="o_wrating_publisher_comment mt-2 mb-2">
                    <div class="o-mail-Message-core position-relative d-flex flex-shrink-0">
                        <div class="o-mail-Message-sidebar d-flex flex-shrink-0">
                            <div class="o-mail-Message-avatarContainer position-relative">
                                <img class="o-mail-Message-avatar w-100 h-100 rounded" t-att-src="message.rating.publisher_avatar" t-att-class="authorAvatarAttClass"/>
                            </div>
                        </div>
                        <div class="w-100 o-min-width-0">
                            <div class="o-mail-Message-header d-flex flex-wrap align-items-baseline mb-1 lh-1">
                                <strong class="me-1 text-truncate"><t t-esc="message.rating.publisher_name"/></strong>
                                <small class="text-muted opacity-50 me-2">Published on <t t-esc="message.rating.publisher_datetime"/></small>
                                <div t-if="store.self.is_user_publisher" class="d-flex rounded-0">
                                    <Dropdown>
                                        <button class="bg-transparent border-0">
                                            <i class="btn px-1 py-0 fa fa-ellipsis-v"/>
                                        </button>
                                        <t t-set-slot="content">
                                            <DropdownItem class="'o_wrating_js_publisher_comment_edit'" onSelected="() =&gt; this.onClikEditComment()">
                                                <i class="fa fa-fw fa-pencil me-1"/>Edit
                                            </DropdownItem>
                                            <DropdownItem class="'o_wrating_js_publisher_comment_delete'" onSelected="() =&gt; this.deleteComment()">
                                                <i class="fa fa-fw fa-trash-o me-1"/>Delete
                                            </DropdownItem>
                                        </t>
                                    </Dropdown>
                                </div>
                            </div>
                            <div class="position-relative d-flex">
                                <div class="o-min-width-0 position-relative d-flex overflow-x-auto d-inline-block">
                                    <div class="o-mail-Message-bubble rounded-bottom-3 position-absolute top-0 start-0 w-100 h-100 bg-info-light opacity-25 rounded-end-3"/>
                                    <div class="position-relative text-break o-mail-Message-body mb-0 py-2 px-3 align-self-start rounded-end-3 rounded-bottom-3">
                                        <p><t t-out="message.rating.publisher_comment"/></p>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </xpath>
    </t>
`);
registerTemplateExtension("portal.Chatter", `/portal_rating/static/src/chatter/frontend/portal_chatter_patch.xml`, `<t t-inherit="portal.Chatter" t-inherit-mode="extension" xml:space="preserve">
        <xpath expr="//div[hasclass('o-mail-Chatter-top')]/*[1]" position="before">
            <div t-if="env.displayRating and ratingStats and ratingStats.total" class="my-2" t-att-class="{ 'mb-4':props.twoColumns }">
                <t t-call="portal_rating.rating_card">
                    <t t-set="two_columns" t-value="props.twoColumns"/>
                    <t t-set="rating_stats" t-value="ratingStats"/>
                    <t t-set="selected_rating" t-value="state.thread?.selectedRating"/>
                </t>
            </div>
        </xpath>
    </t>
`);
                    });
