Changeset 3293513
- Timestamp:
- 05/14/2025 08:30:41 PM (11 months ago)
- Location:
- groundhogg
- Files:
-
- 18 edited
- 1 copied
-
tags/4.1.2.1 (copied) (copied from groundhogg/trunk)
-
tags/4.1.2.1/README.txt (modified) (2 diffs)
-
tags/4.1.2.1/assets/js/admin/contacts/contact-editor.js (modified) (1 diff)
-
tags/4.1.2.1/assets/js/admin/contacts/contact-editor.min.js (modified) (1 diff)
-
tags/4.1.2.1/assets/js/admin/data.js (modified) (1 diff)
-
tags/4.1.2.1/assets/js/admin/data.min.js (modified) (1 diff)
-
tags/4.1.2.1/assets/js/admin/features/send-broadcast.js (modified) (1 diff)
-
tags/4.1.2.1/assets/js/admin/features/send-broadcast.min.js (modified) (1 diff)
-
tags/4.1.2.1/groundhogg.php (modified) (2 diffs)
-
tags/4.1.2.1/includes/classes/step.php (modified) (1 diff)
-
trunk/README.txt (modified) (2 diffs)
-
trunk/assets/js/admin/contacts/contact-editor.js (modified) (1 diff)
-
trunk/assets/js/admin/contacts/contact-editor.min.js (modified) (1 diff)
-
trunk/assets/js/admin/data.js (modified) (1 diff)
-
trunk/assets/js/admin/data.min.js (modified) (1 diff)
-
trunk/assets/js/admin/features/send-broadcast.js (modified) (1 diff)
-
trunk/assets/js/admin/features/send-broadcast.min.js (modified) (1 diff)
-
trunk/groundhogg.php (modified) (2 diffs)
-
trunk/includes/classes/step.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
groundhogg/tags/4.1.2.1/README.txt
r3289364 r3293513 7 7 Tested up to: 6.8 8 8 Requires PHP: 7.1 9 Stable tag: 4.1.2 9 Stable tag: 4.1.2.1 10 10 License: GPLv3 11 11 License URI: https://www.gnu.org/licenses/gpl.md … … 350 350 351 351 == Changelog == 352 353 = 4.1.2.1 (2025-05-14) = 354 * TWEAKED Changed "Fixed Segment" to "Static Segment". 355 * FIXED Contact activity timeline not loading in some cases if related resource was deleted. 356 * FIXED The same flow step appearing in multiple different trigger branches if the trigger IDs start with the same number. 352 357 353 358 = 4.1.2 (2025-05-06) = -
groundhogg/tags/4.1.2.1/assets/js/admin/contacts/contact-editor.js
r3264477 r3293513 1153 1153 ] 1154 1154 1155 Promise.all(promises). then(() => {1155 Promise.all(promises).catch(err => {}).finally(() => { 1156 1156 $el.html(this.render(activities)) 1157 1157 this.onMount() 1158 }).catch(e => {1159 // Something went wrong1160 console.log(e)1161 1158 }) 1162 1159 -
groundhogg/tags/4.1.2.1/assets/js/admin/contacts/contact-editor.min.js
r3264477 r3293513 121 121 <ul id="activity-timeline"> 122 122 ${activities.map(a=>{try{return this.renderActivity(a)}catch(e){return""}}).join("")} 123 </ul>`},onMount(){$(".event-queue-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventQueue.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Now")},{key:"cancel",text:`<span class="gh-text danger">${__("Cancel")}</span>`}],onSelect:key=>{switch(key){case"cancel":patch(`${EventQueue.route}/${event.ID}/cancel`).then(()=>{EventQueue.items.splice(EventQueue.items.findIndex(e=>e.ID===event.ID),1);dialog({message:__("Event cancelled","groundhogg")});this.needsRefresh()});break;case"execute":patch(`${EventQueue.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})});$(".event-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventsStore.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Again")}],onSelect:key=>{switch(key){case"execute":patch(`${EventsStore.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})})},mount(selector,activities,{needsRefresh:needsRefresh=()=>{}}){this.needsRefresh=needsRefresh;const $el=$(selector);if(!activities.length){$el.html(`<div class="align-center-space-between" style="margin: 20px"><span class="pill orange">${__("No activity found.","groundhogg")}</span></div>`);return}let funnelIds=activities.reduce((arr,e)=>{let funnelId=parseInt(e.data?.funnel_id||e.form?.data?.funnel_id);if(funnelId>1){if(!arr.includes(funnelId)){arr.push(funnelId)}}return arr},[]);let emailIds=activities.reduce((arr,e)=>{let emailId=parseInt(e.data?.email_id);if(emailId>1){if(!arr.includes(emailId)){arr.push(emailId)}}return arr},[]);activities.filter(a=>a.type==="event"&&a.data.event_type==2).forEach(a=>BroadcastsStore.itemsFetched([a.broadcast]));let promises=[...activities.filter(a=>a.type==="activity"&&this.types[a.data.activity_type]?.hasOwnProperty("preload")).map(a=>this.types[a.data.activity_type]?.preload(a)),funnelIds.length&&!FunnelsStore.hasItems(funnelIds)?FunnelsStore.maybeFetchItems(funnelIds):null,emailIds.length&&!EmailsStore.hasItems(emailIds)?EmailsStore.maybeFetchItems(emailIds):null];Promise.all(promises). then(()=>{$el.html(this.render(activities));this.onMount()}).catch(e=>{console.log(e)})}};const otherContactStuff=()=>{let activeTab=editor.default_tab??"activity";const tabs=[{id:"activity",name:__("Activity"),render:()=>{return`123 </ul>`},onMount(){$(".event-queue-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventQueue.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Now")},{key:"cancel",text:`<span class="gh-text danger">${__("Cancel")}</span>`}],onSelect:key=>{switch(key){case"cancel":patch(`${EventQueue.route}/${event.ID}/cancel`).then(()=>{EventQueue.items.splice(EventQueue.items.findIndex(e=>e.ID===event.ID),1);dialog({message:__("Event cancelled","groundhogg")});this.needsRefresh()});break;case"execute":patch(`${EventQueue.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})});$(".event-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventsStore.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Again")}],onSelect:key=>{switch(key){case"execute":patch(`${EventsStore.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})})},mount(selector,activities,{needsRefresh:needsRefresh=()=>{}}){this.needsRefresh=needsRefresh;const $el=$(selector);if(!activities.length){$el.html(`<div class="align-center-space-between" style="margin: 20px"><span class="pill orange">${__("No activity found.","groundhogg")}</span></div>`);return}let funnelIds=activities.reduce((arr,e)=>{let funnelId=parseInt(e.data?.funnel_id||e.form?.data?.funnel_id);if(funnelId>1){if(!arr.includes(funnelId)){arr.push(funnelId)}}return arr},[]);let emailIds=activities.reduce((arr,e)=>{let emailId=parseInt(e.data?.email_id);if(emailId>1){if(!arr.includes(emailId)){arr.push(emailId)}}return arr},[]);activities.filter(a=>a.type==="event"&&a.data.event_type==2).forEach(a=>BroadcastsStore.itemsFetched([a.broadcast]));let promises=[...activities.filter(a=>a.type==="activity"&&this.types[a.data.activity_type]?.hasOwnProperty("preload")).map(a=>this.types[a.data.activity_type]?.preload(a)),funnelIds.length&&!FunnelsStore.hasItems(funnelIds)?FunnelsStore.maybeFetchItems(funnelIds):null,emailIds.length&&!EmailsStore.hasItems(emailIds)?EmailsStore.maybeFetchItems(emailIds):null];Promise.all(promises).catch(err=>{}).finally(()=>{$el.html(this.render(activities));this.onMount()})}};const otherContactStuff=()=>{let activeTab=editor.default_tab??"activity";const tabs=[{id:"activity",name:__("Activity"),render:()=>{return` 124 124 <div class="gh-panel top-left-square"> 125 125 <div class="inside"> -
groundhogg/tags/4.1.2.1/assets/js/admin/data.js
r3264477 r3293513 27 27 28 28 if (!response.ok) { 29 console.log(json)30 29 throw new ApiError(json.message, json.code) 31 30 } -
groundhogg/tags/4.1.2.1/assets/js/admin/data.min.js
r3264477 r3293513 1 (function($){function ApiError(message,code="error"){this.name="ApiError";this.message=message;this.code=code}ApiError.prototype=Error.prototype;async function apiGet(route,params={},opts={}){const response=await fetch(route+"?"+$.param(params),{headers:{"X-WP-Nonce":wpApiSettings.nonce},...opts});let json=await response.json();if(!response.ok){ console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiPost(url="",data={},opts={}){const response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data),...opts});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiPatch(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"PATCH",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiDelete(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"DELETE",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}const apiPostFormData=async(url,data,opts={})=>{const response=await fetch(url,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":wpApiSettings.nonce},body:data,...opts});return response.json()};async function adminAjax(data={},opts={}){if(!(data instanceof FormData)){const fData=new FormData;for(const key in data){if(data.hasOwnProperty(key)){fData.append(key,data[key])}}data=fData}let ajaxUrl=opts.url??ajaxurl;delete opts.url;data.append("gh_admin_ajax_nonce",Groundhogg.nonces._adminajax);const response=await fetch(ajaxUrl,{method:"POST",credentials:"same-origin",body:data,...opts});return response.json()}const ObjectStore=(route,extra={})=>({primaryKey:"ID",getItemFromResponse:r=>r.item,getItemsFromResponse:r=>r.items??[],getTotalItemsFromResponse:r=>r.total_items,items:[],total_items:0,route:route,cache:{},getResultsFromCache(query={}){let[results=[],totalItems=0]=this.cache[JSON.stringify(query)]??[];this.total_items=totalItems;return results.map(id=>this.get(id))},clearResultsCache(key=""){this.cache={}},setInResultsCache(query,results=[],totalItems=0){this.cache[JSON.stringify(query)]=[results.map(item=>item[this.primaryKey]),totalItems]},hasCachedResults(query){return JSON.stringify(query)in this.cache},get(id){return this.items.find(item=>item[this.primaryKey]==id)},getItems(){return this.items},has(id){return this.hasItem(id)},hasItem(id){return this.items.some(item=>item[this.primaryKey]==id)},hasItems(itemIds=[]){if(!itemIds||itemIds.length===0){return this.items.length>0}for(let i=0;i<itemIds.length;i++){const itemId=itemIds[i];if(!this.items.find(item=>{return item[this.primaryKey]==itemId})){return false}}return true},getTotalItems(){return this.total_items},itemsFetched(items){if(!Array.isArray(items)){return}this.items=[...items,...this.items.filter(item=>!items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))]},clearItems(){this.items=[]},find(f=()=>{}){return this.items.find(f)},filter(f=()=>{}){return this.items.filter(f)},async fetchItems(params,opts={}){if(this.hasCachedResults(params)){return this.getResultsFromCache(params)}return apiGet(this.route,params,opts).then(r=>{this.total_items=this.getTotalItemsFromResponse(r);return this.getItemsFromResponse(r)}).then(items=>{this.itemsFetched(items);this.setInResultsCache(params,items,this.total_items);return items})},async maybeFetchItems(ids=[],opts={}){const{param:param=this.primaryKey,...otherOpts}=opts;if((!ids||ids.length===0)&&this.hasItems()){return this.items}if(ids&&ids.length>0&&ids.every(id=>this.hasItem(id))){return ids.map(id=>this.get(id))}if(!ids||!ids.length){return this.fetchItems({},opts)}let missingIds=ids.filter(id=>!this.hasItem(id));const params={[param]:missingIds,limit:missingIds.length};return this.fetchItems(params,otherOpts)},async fetchItem(id,opts={}){return apiGet(`${this.route}/${id}`,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async maybeFetchItem(id,opts={}){if(this.hasItem(id)){return this.get(id)}return this.fetchItem(id,opts)},async create(...args){return this.post(...args)},async createMany(...args){return this.postMany(...args)},async post(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.clearResultsCache();this.itemsFetched([item]);return item})},async postMany(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.clearResultsCache();this.itemsFetched(items);return items})},async update(...args){return this.patch(...args)},async patch(id,data,opts={}){return apiPatch(`${this.route}/${id}`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMany(items,opts={}){return apiPatch(`${this.route}`,items,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},async duplicate(id,data,opts={}){return apiPost(`${this.route}/${id}/duplicate`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMeta(id,data,opts={}){return apiPatch(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async deleteMeta(id,data,opts={}){return apiDelete(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async fetchRelationships(id,{other_type,...rest},opts={}){return apiGet(`${this.route}/${id}/relationships`,{other_type:other_type,...rest},opts).then(r=>this.getItemsFromResponse(r))},async createRelationships(id,data,opts={}){return apiPost(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async deleteRelationships(id,data,opts={}){return apiDelete(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async count(params){return apiGet(`${this.route}`,{count:true,...params}).then(r=>r.total_items)},async delete(id){this.clearResultsCache();if(typeof id=="object"){return this.deleteMany(id)}return apiDelete(`${this.route}/${id}`).then(r=>{this.items=[...this.items.filter(item=>item[this.primaryKey]!=id)];return r})},async deleteMany(query){this.clearResultsCache();return apiDelete(`${this.route}`,query).then(r=>{let items=this.getItemsFromResponse(r);this.items=[...this.items.filter(item=>!items.includes(item[this.primaryKey]))];return r})},...extra});Groundhogg.api.post=apiPost;Groundhogg.api.postFormData=apiPostFormData;Groundhogg.api.get=apiGet;Groundhogg.api.patch=apiPatch;Groundhogg.api.delete=apiDelete;Groundhogg.api.ajax=adminAjax;Groundhogg.api.ApiError=ApiError;Groundhogg.stores={options:{items:{},route:Groundhogg.api.routes.v4.options,get(opt,_default=false){if(Array.isArray(opt)){let opts={};opt.forEach(_opt=>opts[_opt]=this.items[_opt]);return opts}return this.items[opt]?this.items[opt]:_default},fetch(data=[],opts={}){let req={};data.forEach(opt=>req[opt]=1);return apiGet(this.route,req,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},post(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},patch(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},delete(data={},opts={}){return apiDelete(this.route,data,opts).then(r=>{data.forEach(opt=>{delete this.items[opt]})})}},tags:ObjectStore(Groundhogg.api.routes.v4.tags,{limit:100,offset:0,async validate(maybeTags){var self=this;return await apiPost(`${this.route}/validate`,maybeTags).then(r=>{if(!r.items){return[]}self.items=[...r.items,...self.items.filter(item=>!r.items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))];return r.items})},preloadTags(){var self=this;return apiGet(this.route,{limit:self.limit,offet:self.offset}).then(data=>{if(!data.items){return}Object.assign(self.items,data.items);if(data.items.length==self.limit){self.offset+=self.limit;self.preloadTags()}})}}),forms:ObjectStore(Groundhogg.api.routes.v4.forms),contacts:ObjectStore(Groundhogg.api.routes.v4.contacts,{async fetchFiles(id,opts={}){return apiGet(`${this.route}/${id}/files`,{},opts).then(r=>this.getItemsFromResponse(r))}}),events:ObjectStore(Groundhogg.api.routes.v4.events),event_queue:ObjectStore(Groundhogg.api.routes.v4.event_queue),page_visits:ObjectStore(Groundhogg.api.routes.v4.page_visits),activity:ObjectStore(Groundhogg.api.routes.v4.activity),campaigns:ObjectStore(Groundhogg.api.routes.v4.campaigns),submissions:ObjectStore(Groundhogg.api.routes.v4.submissions),funnels:ObjectStore(Groundhogg.api.routes.v4.funnels,{async addContacts({query,funnel_id,step_id,...rest},opts={}){return apiPost(`${this.route}/${funnel_id}/start`,{query:query,step_id:step_id,funnel_id:funnel_id,...rest},opts).then(d=>d.added)},async commit(id,data,opts={}){return apiPost(`${this.route}/${id}/commit`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},isStartingStep(funnelId,stepId,checkEdited=false){return!this.getPrecedingSteps(funnelId,stepId,checkEdited).find(_step=>_step.data.step_group=="action")},getSteps(funnelId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;return checkEdited&&funnel.meta.edited?funnel.meta.edited.steps:funnel.steps},getFunnelAndStep(funnelId,stepId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;const step=checkEdited&&funnel.meta.edited?funnel.meta.edited.steps.find(s=>s.ID==stepId):funnel.steps.find(s=>s.ID==stepId);return{funnel:funnel,step:step}},getProceedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order>step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)},getPrecedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order<step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)}}),emails:ObjectStore(Groundhogg.api.routes.v4.emails,{send(id,data){return apiPost(`${this.route}/${id}/send`,data)}}),broadcasts:ObjectStore(Groundhogg.api.routes.v4.broadcasts),notes:ObjectStore(Groundhogg.api.routes.v4.notes),replies:ObjectStore(Groundhogg.api.routes.v4.notes),tasks:ObjectStore(Groundhogg.api.routes.v4.tasks,{complete(id){return apiPatch(`${this.route}/${id}/complete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},completeMany(ids){return apiPatch(`${this.route}/complete`,ids).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},incomplete(id){return apiPatch(`${this.route}/${id}/incomplete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})}}),searches:ObjectStore(Groundhogg.api.routes.v4.searches,{primaryKey:"id"}),posts:ObjectStore(Groundhogg.api.routes.posts,{primaryKey:"id"}),email_log:ObjectStore(Groundhogg.api.routes.v4.email_log)};Groundhogg.createStore=(id,route="",extra={})=>{const store=ObjectStore(route,extra);Groundhogg.stores[id]=store;return store};const createState=(initialState={})=>new Proxy({initial:{...initialState},state:{...initialState},set(newState){this.state={...this.state,...newState}},clear(){this.state={}},reset(){this.set({...this.initial})},get(key=""){if(key){return this.state[key]}return this.state},has(key=""){if(key){return key in this.state}return Object.keys(this.state).length>0}},{set(manager,key,val){if(key==="state"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"state"),key,val)},get(manager,key,receiver){if(key==="state"){return Reflect.get(manager,key)}let state=Reflect.get(manager,"state");if(Reflect.has(state,key)){return Reflect.get(state,key)}return Reflect.get(manager,key)}});const stateMap=new WeakMap;function useState(initialState,caller=false){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,createState(initialState))}return stateMap.get(caller)}function bindState(state,caller){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,state)}}const createRegistry=(initialItems={})=>new Proxy({items:{...initialItems},add(key,newItem){this.items[key]=newItem},clear(){this.items={}},get(key=""){if(key){return this.items[key]}return this.items},keys(){return Object.keys(this.items)},filter(func){return this.keys().filter(key=>func(this[key],key))},map(func){return this.keys().map(key=>func(this[key],key))},has(key=""){if(key){return key in this.items}return Object.keys(this.items).length>0}},{set(manager,key,val){if(key==="items"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"items"),key,val)},get(manager,key,receiver){if(key==="items"){return Reflect.get(manager,key)}let items=Reflect.get(manager,"items");if(Reflect.has(items,key)){return Reflect.get(items,key)}return Reflect.get(manager,key)}});Groundhogg.createState=createState;Groundhogg.useState=useState;Groundhogg.bindState=bindState;Groundhogg.createRegistry=createRegistry})(jQuery);1 (function($){function ApiError(message,code="error"){this.name="ApiError";this.message=message;this.code=code}ApiError.prototype=Error.prototype;async function apiGet(route,params={},opts={}){const response=await fetch(route+"?"+$.param(params),{headers:{"X-WP-Nonce":wpApiSettings.nonce},...opts});let json=await response.json();if(!response.ok){throw new ApiError(json.message,json.code)}return json}async function apiPost(url="",data={},opts={}){const response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data),...opts});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiPatch(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"PATCH",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiDelete(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"DELETE",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}const apiPostFormData=async(url,data,opts={})=>{const response=await fetch(url,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":wpApiSettings.nonce},body:data,...opts});return response.json()};async function adminAjax(data={},opts={}){if(!(data instanceof FormData)){const fData=new FormData;for(const key in data){if(data.hasOwnProperty(key)){fData.append(key,data[key])}}data=fData}let ajaxUrl=opts.url??ajaxurl;delete opts.url;data.append("gh_admin_ajax_nonce",Groundhogg.nonces._adminajax);const response=await fetch(ajaxUrl,{method:"POST",credentials:"same-origin",body:data,...opts});return response.json()}const ObjectStore=(route,extra={})=>({primaryKey:"ID",getItemFromResponse:r=>r.item,getItemsFromResponse:r=>r.items??[],getTotalItemsFromResponse:r=>r.total_items,items:[],total_items:0,route:route,cache:{},getResultsFromCache(query={}){let[results=[],totalItems=0]=this.cache[JSON.stringify(query)]??[];this.total_items=totalItems;return results.map(id=>this.get(id))},clearResultsCache(key=""){this.cache={}},setInResultsCache(query,results=[],totalItems=0){this.cache[JSON.stringify(query)]=[results.map(item=>item[this.primaryKey]),totalItems]},hasCachedResults(query){return JSON.stringify(query)in this.cache},get(id){return this.items.find(item=>item[this.primaryKey]==id)},getItems(){return this.items},has(id){return this.hasItem(id)},hasItem(id){return this.items.some(item=>item[this.primaryKey]==id)},hasItems(itemIds=[]){if(!itemIds||itemIds.length===0){return this.items.length>0}for(let i=0;i<itemIds.length;i++){const itemId=itemIds[i];if(!this.items.find(item=>{return item[this.primaryKey]==itemId})){return false}}return true},getTotalItems(){return this.total_items},itemsFetched(items){if(!Array.isArray(items)){return}this.items=[...items,...this.items.filter(item=>!items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))]},clearItems(){this.items=[]},find(f=()=>{}){return this.items.find(f)},filter(f=()=>{}){return this.items.filter(f)},async fetchItems(params,opts={}){if(this.hasCachedResults(params)){return this.getResultsFromCache(params)}return apiGet(this.route,params,opts).then(r=>{this.total_items=this.getTotalItemsFromResponse(r);return this.getItemsFromResponse(r)}).then(items=>{this.itemsFetched(items);this.setInResultsCache(params,items,this.total_items);return items})},async maybeFetchItems(ids=[],opts={}){const{param:param=this.primaryKey,...otherOpts}=opts;if((!ids||ids.length===0)&&this.hasItems()){return this.items}if(ids&&ids.length>0&&ids.every(id=>this.hasItem(id))){return ids.map(id=>this.get(id))}if(!ids||!ids.length){return this.fetchItems({},opts)}let missingIds=ids.filter(id=>!this.hasItem(id));const params={[param]:missingIds,limit:missingIds.length};return this.fetchItems(params,otherOpts)},async fetchItem(id,opts={}){return apiGet(`${this.route}/${id}`,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async maybeFetchItem(id,opts={}){if(this.hasItem(id)){return this.get(id)}return this.fetchItem(id,opts)},async create(...args){return this.post(...args)},async createMany(...args){return this.postMany(...args)},async post(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.clearResultsCache();this.itemsFetched([item]);return item})},async postMany(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.clearResultsCache();this.itemsFetched(items);return items})},async update(...args){return this.patch(...args)},async patch(id,data,opts={}){return apiPatch(`${this.route}/${id}`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMany(items,opts={}){return apiPatch(`${this.route}`,items,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},async duplicate(id,data,opts={}){return apiPost(`${this.route}/${id}/duplicate`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMeta(id,data,opts={}){return apiPatch(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async deleteMeta(id,data,opts={}){return apiDelete(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async fetchRelationships(id,{other_type,...rest},opts={}){return apiGet(`${this.route}/${id}/relationships`,{other_type:other_type,...rest},opts).then(r=>this.getItemsFromResponse(r))},async createRelationships(id,data,opts={}){return apiPost(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async deleteRelationships(id,data,opts={}){return apiDelete(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async count(params){return apiGet(`${this.route}`,{count:true,...params}).then(r=>r.total_items)},async delete(id){this.clearResultsCache();if(typeof id=="object"){return this.deleteMany(id)}return apiDelete(`${this.route}/${id}`).then(r=>{this.items=[...this.items.filter(item=>item[this.primaryKey]!=id)];return r})},async deleteMany(query){this.clearResultsCache();return apiDelete(`${this.route}`,query).then(r=>{let items=this.getItemsFromResponse(r);this.items=[...this.items.filter(item=>!items.includes(item[this.primaryKey]))];return r})},...extra});Groundhogg.api.post=apiPost;Groundhogg.api.postFormData=apiPostFormData;Groundhogg.api.get=apiGet;Groundhogg.api.patch=apiPatch;Groundhogg.api.delete=apiDelete;Groundhogg.api.ajax=adminAjax;Groundhogg.api.ApiError=ApiError;Groundhogg.stores={options:{items:{},route:Groundhogg.api.routes.v4.options,get(opt,_default=false){if(Array.isArray(opt)){let opts={};opt.forEach(_opt=>opts[_opt]=this.items[_opt]);return opts}return this.items[opt]?this.items[opt]:_default},fetch(data=[],opts={}){let req={};data.forEach(opt=>req[opt]=1);return apiGet(this.route,req,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},post(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},patch(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},delete(data={},opts={}){return apiDelete(this.route,data,opts).then(r=>{data.forEach(opt=>{delete this.items[opt]})})}},tags:ObjectStore(Groundhogg.api.routes.v4.tags,{limit:100,offset:0,async validate(maybeTags){var self=this;return await apiPost(`${this.route}/validate`,maybeTags).then(r=>{if(!r.items){return[]}self.items=[...r.items,...self.items.filter(item=>!r.items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))];return r.items})},preloadTags(){var self=this;return apiGet(this.route,{limit:self.limit,offet:self.offset}).then(data=>{if(!data.items){return}Object.assign(self.items,data.items);if(data.items.length==self.limit){self.offset+=self.limit;self.preloadTags()}})}}),forms:ObjectStore(Groundhogg.api.routes.v4.forms),contacts:ObjectStore(Groundhogg.api.routes.v4.contacts,{async fetchFiles(id,opts={}){return apiGet(`${this.route}/${id}/files`,{},opts).then(r=>this.getItemsFromResponse(r))}}),events:ObjectStore(Groundhogg.api.routes.v4.events),event_queue:ObjectStore(Groundhogg.api.routes.v4.event_queue),page_visits:ObjectStore(Groundhogg.api.routes.v4.page_visits),activity:ObjectStore(Groundhogg.api.routes.v4.activity),campaigns:ObjectStore(Groundhogg.api.routes.v4.campaigns),submissions:ObjectStore(Groundhogg.api.routes.v4.submissions),funnels:ObjectStore(Groundhogg.api.routes.v4.funnels,{async addContacts({query,funnel_id,step_id,...rest},opts={}){return apiPost(`${this.route}/${funnel_id}/start`,{query:query,step_id:step_id,funnel_id:funnel_id,...rest},opts).then(d=>d.added)},async commit(id,data,opts={}){return apiPost(`${this.route}/${id}/commit`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},isStartingStep(funnelId,stepId,checkEdited=false){return!this.getPrecedingSteps(funnelId,stepId,checkEdited).find(_step=>_step.data.step_group=="action")},getSteps(funnelId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;return checkEdited&&funnel.meta.edited?funnel.meta.edited.steps:funnel.steps},getFunnelAndStep(funnelId,stepId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;const step=checkEdited&&funnel.meta.edited?funnel.meta.edited.steps.find(s=>s.ID==stepId):funnel.steps.find(s=>s.ID==stepId);return{funnel:funnel,step:step}},getProceedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order>step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)},getPrecedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order<step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)}}),emails:ObjectStore(Groundhogg.api.routes.v4.emails,{send(id,data){return apiPost(`${this.route}/${id}/send`,data)}}),broadcasts:ObjectStore(Groundhogg.api.routes.v4.broadcasts),notes:ObjectStore(Groundhogg.api.routes.v4.notes),replies:ObjectStore(Groundhogg.api.routes.v4.notes),tasks:ObjectStore(Groundhogg.api.routes.v4.tasks,{complete(id){return apiPatch(`${this.route}/${id}/complete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},completeMany(ids){return apiPatch(`${this.route}/complete`,ids).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},incomplete(id){return apiPatch(`${this.route}/${id}/incomplete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})}}),searches:ObjectStore(Groundhogg.api.routes.v4.searches,{primaryKey:"id"}),posts:ObjectStore(Groundhogg.api.routes.posts,{primaryKey:"id"}),email_log:ObjectStore(Groundhogg.api.routes.v4.email_log)};Groundhogg.createStore=(id,route="",extra={})=>{const store=ObjectStore(route,extra);Groundhogg.stores[id]=store;return store};const createState=(initialState={})=>new Proxy({initial:{...initialState},state:{...initialState},set(newState){this.state={...this.state,...newState}},clear(){this.state={}},reset(){this.set({...this.initial})},get(key=""){if(key){return this.state[key]}return this.state},has(key=""){if(key){return key in this.state}return Object.keys(this.state).length>0}},{set(manager,key,val){if(key==="state"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"state"),key,val)},get(manager,key,receiver){if(key==="state"){return Reflect.get(manager,key)}let state=Reflect.get(manager,"state");if(Reflect.has(state,key)){return Reflect.get(state,key)}return Reflect.get(manager,key)}});const stateMap=new WeakMap;function useState(initialState,caller=false){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,createState(initialState))}return stateMap.get(caller)}function bindState(state,caller){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,state)}}const createRegistry=(initialItems={})=>new Proxy({items:{...initialItems},add(key,newItem){this.items[key]=newItem},clear(){this.items={}},get(key=""){if(key){return this.items[key]}return this.items},keys(){return Object.keys(this.items)},filter(func){return this.keys().filter(key=>func(this[key],key))},map(func){return this.keys().map(key=>func(this[key],key))},has(key=""){if(key){return key in this.items}return Object.keys(this.items).length>0}},{set(manager,key,val){if(key==="items"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"items"),key,val)},get(manager,key,receiver){if(key==="items"){return Reflect.get(manager,key)}let items=Reflect.get(manager,"items");if(Reflect.has(items,key)){return Reflect.get(items,key)}return Reflect.get(manager,key)}});Groundhogg.createState=createState;Groundhogg.useState=useState;Groundhogg.bindState=bindState;Groundhogg.createRegistry=createRegistry})(jQuery); -
groundhogg/tags/4.1.2.1/assets/js/admin/features/send-broadcast.js
r3221208 r3293513 467 467 468 468 }), 469 '<b> FixedSegment:</b> <i>Contacts</i> currently <i>within the segment</i>.',469 '<b>Static Segment:</b> <i>Contacts</i> currently <i>within the segment</i>.', 470 470 ]), 471 471 Label({ -
groundhogg/tags/4.1.2.1/assets/js/admin/features/send-broadcast.min.js
r3221208 r3293513 1 (function($){const{adminPageURL,bold,icons,dialog}=Groundhogg.element;const{emails:EmailsStore,searches:SearchesStore,contacts:ContactsStore,campaigns:CampaignsStore}=Groundhogg.stores;const{routes,post,ajax}=Groundhogg.api;const{createFilters}=Groundhogg.filters.functions;const{formatNumber,formatDateTime}=Groundhogg.formatting;const{debounce}=Groundhogg.functions;const{sprintf,__,_x,_n}=wp.i18n;const{isInTheFuture,getDate,date}=wp.date;const{Div,Button,Pg,Modal,Textarea,Select,ItemPicker,Fragment,Input,InputGroup,Iframe,makeEl,ButtonToggle,Label,Span,Toggle,Dashicon}=MakeEl;const initialState={step:"object",steps:[],object:null,when:"later",campaigns:[],searchMethod:"filters",searchMethods:[],totalContacts:0,date:date("Y-m-d"),time:date("H:00:00",getDate().setHours(getDate().getHours()+1)),broadcast:null,segment_type:"fixed",batching:false,batch_interval:"minutes",batch_interval_length:30,batch_amount:100,send_in_local_time:false};const getSearchMethods=()=>{return[...getState().searchMethods??[],{id:"filters",text:__("Search for contacts using filters.","groundhogg"),query:()=>({filters:getState().include_filters,exclude_filters:getState().exclude_filters})},{id:"all-contacts",text:__("All contacts.","groundhogg"),query:()=>({})},{id:"all-my-contacts",text:__("All contacts assigned to me.","groundhogg"),query:()=>({owner_id:Groundhogg.currentUser.ID})},{id:"confirmed-contacts",text:__("All confirmed contacts.","groundhogg"),query:()=>({optin_status:2})},...SearchesStore.getItems().map(({id,name})=>({id:id,text:sprintf(__("Saved search %s","groundhogg"),bold(name)),query:()=>({saved_search:id})}))]};const State=Groundhogg.createState({...initialState});const getQuery=()=>{let query={};const{searchMethod:searchMethod="filters"}=getState();query=getSearchMethods().find(({id})=>id===searchMethod).query();if(getObject()&&getObject().data.message_type!=="transactional"){query.marketable="yes"}return query};const updateTotalContacts=(morph=true)=>{return ContactsStore.count(getQuery()).then(total=>{setState({totalContacts:total},morph)})};const updateDurationEstimate=debounce(()=>ajax({action:"gh_estimate_send_duration",total_contacts:getState().totalContacts,batch_interval:getState().batch_interval,batch_interval_length:getState().batch_interval_length,batch_amount:getState().batch_amount}).then(r=>{const{time}=r.data;setState({duration_estimate:time})}),500);const getState=()=>State;const setState=(newState,morph=true)=>{State.set({...newState});if(morph){try{morphdom(document.getElementById("broadcast-scheduler"),BroadcastScheduler())}catch(e){console.log(e)}}};const getObject=()=>getState().object;const FromPreview=()=>Div({className:"from-preview display-flex gap-20 has-box-shadow"},[makeEl("img",{src:getObject().context.from_avatar,className:"from-avatar",height:40,width:40,style:{borderRadius:"50%"}}),Div({className:"subject-and-from"},[`<h2>${getObject().data.subject}</h2>`,`<span class="from-name">${getObject().context.from_name}</span> <span class="from-email"><${getObject().context.from_email}></span>`])]);const EmailPreview=()=>{return Div({className:"email-preview display-flex column",style:{overflow:"hidden",border:"1px solid #ccc",borderRadius:"5px"}},[FromPreview(),Iframe({id:`broadcast-email-preview`,height:400,style:{width:"100%"}},getObject().context.built)])};const Steps={object:{name:__("Email","groundhogg"),icon:icons.email,requirements:()=>true,render:()=>{return Fragment([Div({className:"display-flex column gap-10"},[`<p>${__("Select an email to send...","groundhogg")}</p>`,Div({className:"display-flex gap-10"},[ItemPicker({id:`broadcast-select-email`,noneSelected:__("Select an email to send...","groundhogg"),selected:getObject()?{id:getObject().ID,text:getObject().data.title}:[],multiple:false,style:{flexGrow:1},fetchOptions:search=>{return EmailsStore.fetchItems({search:search,status:"ready"}).then(emails=>emails.map(({ID,data})=>({id:ID,text:data.title})))},onChange:item=>{if(!item){setState({object:null});return}let email=EmailsStore.get(item.id);setState({object:email,campaigns:email.campaigns})}}),getObject()?Button({id:"go-to-campaigns",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"campaigns"})}},sprintf("%s →",__("Campaigns","groundhogg"))):null])]),getObject()?EmailPreview():null])}},campaigns:{name:__("Campaigns","groundhogg"),requirements:()=>getObject(),icon:Dashicon("flag"),render:()=>{return Fragment([`<p>${__("Use campaigns to organize your broadcasts! Select one or more campaigns...","groundhogg")}</p>`,ItemPicker({id:"broadcast-campaigns",noneSelected:__("Select a campaign...","groundhogg"),tags:true,selected:getState().campaigns.map(({ID,data})=>({id:ID,text:data.name})),fetchOptions:async search=>{let campaigns=await CampaignsStore.fetchItems({search:search,limit:20});return campaigns.map(({ID,data})=>({id:ID,text:data.name}))},createOption:async id=>{let campaign=await CampaignsStore.create({data:{name:id}});return{id:campaign.ID,text:campaign.data.name}},onChange:items=>setState({campaigns:items.map(({id})=>CampaignsStore.get(id))})}),Button({id:"go-to-schedule",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"contacts"})}},sprintf("%s →",__("Contacts","groundhogg")))])}},contacts:{name:__("Contacts","groundhogg"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date),icon:icons.contact,render:()=>{return Fragment([`<p>${__("Select contacts to receive this broadcast...","groundhogg")}</p>`,ItemPicker({id:"select-search-method",multiple:false,selected:getSearchMethods().find(({id})=>id===getState().searchMethod),fetchOptions:async search=>{return getSearchMethods().filter(({text})=>text.match(new RegExp(search,"i")))},onChange:item=>{if(!item){setState({searchMethod:"filters"});updateTotalContacts();return}let{id}=item;setState({searchMethod:id});updateTotalContacts()}}),getState().searchMethod==="filters"?Div({id:"broadcast-include-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-include-filters",getState().include_filters,include_filters=>{setState({include_filters:include_filters},false);updateTotalContacts()}).init()})}}):null,getState().searchMethod==="filters"?Div({id:"broadcast-exclude-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-exclude-filters",getState().exclude_filters,exclude_filters=>{setState({exclude_filters:exclude_filters},false);updateTotalContacts()}).init()})}}):null,`<div style="font-size: 14px">${sprintf(__("%s contacts will receive this broadcast.","groundhogg"),bold(formatNumber(getState().totalContacts)))}</div>`,State.when==="later"?Fragment([Div({className:"display-flex column gap-5"},[`<p>Which contacts should be included at the time of sending?</p>`,Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="fixed",onChange:e=>{if(e.target.checked){State.set({segment_type:"fixed"})}}}),"<b> FixedSegment:</b> <i>Contacts</i> currently <i>within the segment</i>."]),Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="dynamic",onChange:e=>{if(e.target.checked){State.set({segment_type:"dynamic"})}}}),"<b>Dynamic Segment:</b> <i>Contacts within the segment</i> at the time of sending."])])]):null,Button({id:"go-to-review",className:"gh-button primary",disabled:!getState().totalContacts,style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"schedule"})}},sprintf("%s →",__("Schedule","groundhogg")))])}},schedule:{name:__("Schedule","groundhogg"),icon:Dashicon("calendar"),requirements:()=>getObject(),render:()=>{return Fragment([Div({className:"space-between"},[`<p>${__("When do you want the broadcast to go out?","groundhogg")}</p>`,ButtonToggle({id:"send-when",options:[{id:"later",text:"Later"},{id:"now",text:"Now"}],selected:getState().when,onChange:when=>setState({when:when})})]),getState().when==="later"?Div({className:"gh-input-group"},[Input({type:"date",id:"send-date",name:"date",value:getState().date||"",min:date("Y-m-d"),onChange:e=>setState({date:e.target.value})}),Input({type:"time",id:"send-time",name:"time",value:getState().time||"",onChange:e=>setState({time:e.target.value})}),Button({className:"gh-button grey small",disabled:true},wp.date.getSettings().timezone.abbr||wp.date.getSettings().timezone.string||`UTC${wp.date.getSettings().timezone.offsetFormatted}`)]):null,getState().when==="later"?Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-local"><p>${__("Send in the contact's local time?","groundhogg")}</p></label>`,Toggle({id:"send-in-local",checked:getState().send_in_local_time,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>setState({send_in_local_time:e.target.checked})})]):null,"<div><hr></div>",Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-batches"><p>${__("Send in batches?","groundhogg")}</p></label>`,Toggle({id:"send-in-batches",checked:getState().batching,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>{setState({batching:e.target.checked});if(getState().batching){updateDurationEstimate()}}})]),getState().batching?Fragment([Pg({className:"display-flex gap-5 align-center"},["Send",Input({id:"batch-amount",name:"batch_amount",step:getState().batch_amount<=100?10:50,value:getState().batch_amount,onInput:e=>{setState({batch_amount:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"100px",paddingRight:0}}),"emails every",Input({id:"batch-interval-length",name:"batch_interval_length",step:getState().batch_interval==="minutes"?5:1,value:getState().batch_interval_length,onInput:e=>{setState({batch_interval_length:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"60px",paddingRight:0}}),Select({id:"batch-interval",name:"batch_interval",selected:getState().batch_interval,onChange:e=>{setState({batch_interval:e.target.value},false);updateDurationEstimate()},options:{minutes:__("Minutes"),hours:__("Hours"),days:__("Days")}})]),getState().duration_estimate?Span({className:"pill yellow"},sprintf(__("It will take at least %s to send to %s contacts.","groundhogg"),bold(getState().duration_estimate),formatNumber(getState().totalContacts))):Span({},__("Estimating...","groundhogg"))]):null,"<div><hr></div>",Button({id:"go-to-contacts",className:"gh-button primary",disabled:getState().when==="later"&&!isInTheFuture(`${getState().date} ${getState().time}`),style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"review"})}},sprintf("%s →",__("Review","groundhogg")))])}},review:{name:"Review",icon:Dashicon("thumbs-up"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date)&&getState().totalContacts,render:()=>{let preview;if(getState().when==="now"){preview=sprintf(__("Send %1$s to %2$s contacts <b>now</b>!","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)))}else{preview=sprintf(__("Send %1$s to %2$s contacts on %3$s.","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)),formatDateTime(`${getState().date} ${getState().time}`))}return Fragment([`<p>${preview}</p>`,getObject()?EmailPreview():null,Button({id:"confirm-and-schedule",className:"gh-button primary medium",onClick:e=>{e.target.innerHTML=`<span class="gh-spinner"></span>`;const{when:when="now",date:date="",time:time="",send_in_local_time:send_in_local_time=false,campaigns:campaigns=[],segment_type:segment_type="fixed",batching:batching=false,batch_interval,batch_interval_length,batch_amount}=getState();post(routes.v4.broadcasts,{object_id:getObject().ID,object_type:"email",query:getQuery(),date:date,time:time,send_now:when==="now",send_in_local_time:send_in_local_time,campaigns:campaigns.map(({ID})=>ID),segment_type:segment_type,batching:batching,batch_interval:batch_interval,batch_interval_length:batch_interval_length,batch_amount:batch_amount}).then(r=>{setState({step:"scheduled",broadcast:r.item})}).catch(err=>{dialog({message:err.message,type:"error"});console.log(err);switch(err.code){case"invalid_date":setState({step:"schedule"});break;default:setState({});break}})}},__("Confirm and schedule!","groundhogg"))])}},scheduled:{name:__("Scheduled"),icon:Dashicon("megaphone"),requirements:()=>getState().broadcast,render:()=>{return Fragment([`<p>${__("🎉 Your broadcast is being scheduled in the background!","groundhogg")}</p>`,Button({id:"re-schedule",className:"gh-button primary",style:{alignSelf:"flex-start"},onClick:e=>{setState({...initialState})}},sprintf("← %s",__("Schedule another broadcast","groundhogg")))])}}};const getSteps=()=>{const merged={};const overrides=getState().steps;for(let step in Steps){if(overrides.hasOwnProperty(step)){merged[step]={...Steps[step],...overrides[step]}}else{merged[step]=Steps[step]}}return merged};const BroadcastScheduler=()=>{const order=["object","campaigns","contacts","schedule","review","scheduled"];return Div({id:"broadcast-scheduler",className:"display-flex column gap-10",style:{width:"500px",maxWidth:"100%"}},[getState().step!=="scheduled"?Div({className:"gh-step-nav",style:{marginBottom:"20px"}},[...order.map(step=>Button({id:`select-${step}`,className:`gh-button icon ${getState().step===step?"primary":"secondary"}`,disabled:!getSteps()[step].requirements(),onClick:e=>{setState({step:step})}},getSteps()[step].icon)).reduce((steps,step,i)=>{if(i>0){steps.push(makeEl("hr",{className:"gh-step-nav-join"}))}steps.push(step);return steps},[])]):null,getSteps()[getState().step].render({getState:getState,getObject:getObject,setState:setState,getQuery:getQuery})])};Groundhogg.BroadcastScheduler=(newState={})=>{SearchesStore.maybeFetchItems();State.reset();setState({...newState},false);if(getState().searchMethod!=="filters"){updateTotalContacts(false)}return BroadcastScheduler()};Groundhogg.SendBroadcast=(selector,{email:email=false,...rest}={},{onScheduled:onScheduled=()=>{}})=>{document.querySelector(selector).append(Groundhogg.BroadcastScheduler({object:email,onScheduled:onScheduled}))};$(()=>{$("#gh-schedule-broadcast").on("click",e=>{e.preventDefault();Modal({},()=>Groundhogg.BroadcastScheduler())});if(typeof GroundhoggNewBroadcast!=="undefined"){document.getElementById("gh-broadcast-form-inline").append(Groundhogg.BroadcastScheduler({object:GroundhoggNewBroadcast.email,onScheduled:()=>{window.location.href=adminPageURL("gh_broadcasts",{status:"scheduled"})}}))}})})(jQuery);1 (function($){const{adminPageURL,bold,icons,dialog}=Groundhogg.element;const{emails:EmailsStore,searches:SearchesStore,contacts:ContactsStore,campaigns:CampaignsStore}=Groundhogg.stores;const{routes,post,ajax}=Groundhogg.api;const{createFilters}=Groundhogg.filters.functions;const{formatNumber,formatDateTime}=Groundhogg.formatting;const{debounce}=Groundhogg.functions;const{sprintf,__,_x,_n}=wp.i18n;const{isInTheFuture,getDate,date}=wp.date;const{Div,Button,Pg,Modal,Textarea,Select,ItemPicker,Fragment,Input,InputGroup,Iframe,makeEl,ButtonToggle,Label,Span,Toggle,Dashicon}=MakeEl;const initialState={step:"object",steps:[],object:null,when:"later",campaigns:[],searchMethod:"filters",searchMethods:[],totalContacts:0,date:date("Y-m-d"),time:date("H:00:00",getDate().setHours(getDate().getHours()+1)),broadcast:null,segment_type:"fixed",batching:false,batch_interval:"minutes",batch_interval_length:30,batch_amount:100,send_in_local_time:false};const getSearchMethods=()=>{return[...getState().searchMethods??[],{id:"filters",text:__("Search for contacts using filters.","groundhogg"),query:()=>({filters:getState().include_filters,exclude_filters:getState().exclude_filters})},{id:"all-contacts",text:__("All contacts.","groundhogg"),query:()=>({})},{id:"all-my-contacts",text:__("All contacts assigned to me.","groundhogg"),query:()=>({owner_id:Groundhogg.currentUser.ID})},{id:"confirmed-contacts",text:__("All confirmed contacts.","groundhogg"),query:()=>({optin_status:2})},...SearchesStore.getItems().map(({id,name})=>({id:id,text:sprintf(__("Saved search %s","groundhogg"),bold(name)),query:()=>({saved_search:id})}))]};const State=Groundhogg.createState({...initialState});const getQuery=()=>{let query={};const{searchMethod:searchMethod="filters"}=getState();query=getSearchMethods().find(({id})=>id===searchMethod).query();if(getObject()&&getObject().data.message_type!=="transactional"){query.marketable="yes"}return query};const updateTotalContacts=(morph=true)=>{return ContactsStore.count(getQuery()).then(total=>{setState({totalContacts:total},morph)})};const updateDurationEstimate=debounce(()=>ajax({action:"gh_estimate_send_duration",total_contacts:getState().totalContacts,batch_interval:getState().batch_interval,batch_interval_length:getState().batch_interval_length,batch_amount:getState().batch_amount}).then(r=>{const{time}=r.data;setState({duration_estimate:time})}),500);const getState=()=>State;const setState=(newState,morph=true)=>{State.set({...newState});if(morph){try{morphdom(document.getElementById("broadcast-scheduler"),BroadcastScheduler())}catch(e){console.log(e)}}};const getObject=()=>getState().object;const FromPreview=()=>Div({className:"from-preview display-flex gap-20 has-box-shadow"},[makeEl("img",{src:getObject().context.from_avatar,className:"from-avatar",height:40,width:40,style:{borderRadius:"50%"}}),Div({className:"subject-and-from"},[`<h2>${getObject().data.subject}</h2>`,`<span class="from-name">${getObject().context.from_name}</span> <span class="from-email"><${getObject().context.from_email}></span>`])]);const EmailPreview=()=>{return Div({className:"email-preview display-flex column",style:{overflow:"hidden",border:"1px solid #ccc",borderRadius:"5px"}},[FromPreview(),Iframe({id:`broadcast-email-preview`,height:400,style:{width:"100%"}},getObject().context.built)])};const Steps={object:{name:__("Email","groundhogg"),icon:icons.email,requirements:()=>true,render:()=>{return Fragment([Div({className:"display-flex column gap-10"},[`<p>${__("Select an email to send...","groundhogg")}</p>`,Div({className:"display-flex gap-10"},[ItemPicker({id:`broadcast-select-email`,noneSelected:__("Select an email to send...","groundhogg"),selected:getObject()?{id:getObject().ID,text:getObject().data.title}:[],multiple:false,style:{flexGrow:1},fetchOptions:search=>{return EmailsStore.fetchItems({search:search,status:"ready"}).then(emails=>emails.map(({ID,data})=>({id:ID,text:data.title})))},onChange:item=>{if(!item){setState({object:null});return}let email=EmailsStore.get(item.id);setState({object:email,campaigns:email.campaigns})}}),getObject()?Button({id:"go-to-campaigns",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"campaigns"})}},sprintf("%s →",__("Campaigns","groundhogg"))):null])]),getObject()?EmailPreview():null])}},campaigns:{name:__("Campaigns","groundhogg"),requirements:()=>getObject(),icon:Dashicon("flag"),render:()=>{return Fragment([`<p>${__("Use campaigns to organize your broadcasts! Select one or more campaigns...","groundhogg")}</p>`,ItemPicker({id:"broadcast-campaigns",noneSelected:__("Select a campaign...","groundhogg"),tags:true,selected:getState().campaigns.map(({ID,data})=>({id:ID,text:data.name})),fetchOptions:async search=>{let campaigns=await CampaignsStore.fetchItems({search:search,limit:20});return campaigns.map(({ID,data})=>({id:ID,text:data.name}))},createOption:async id=>{let campaign=await CampaignsStore.create({data:{name:id}});return{id:campaign.ID,text:campaign.data.name}},onChange:items=>setState({campaigns:items.map(({id})=>CampaignsStore.get(id))})}),Button({id:"go-to-schedule",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"contacts"})}},sprintf("%s →",__("Contacts","groundhogg")))])}},contacts:{name:__("Contacts","groundhogg"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date),icon:icons.contact,render:()=>{return Fragment([`<p>${__("Select contacts to receive this broadcast...","groundhogg")}</p>`,ItemPicker({id:"select-search-method",multiple:false,selected:getSearchMethods().find(({id})=>id===getState().searchMethod),fetchOptions:async search=>{return getSearchMethods().filter(({text})=>text.match(new RegExp(search,"i")))},onChange:item=>{if(!item){setState({searchMethod:"filters"});updateTotalContacts();return}let{id}=item;setState({searchMethod:id});updateTotalContacts()}}),getState().searchMethod==="filters"?Div({id:"broadcast-include-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-include-filters",getState().include_filters,include_filters=>{setState({include_filters:include_filters},false);updateTotalContacts()}).init()})}}):null,getState().searchMethod==="filters"?Div({id:"broadcast-exclude-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-exclude-filters",getState().exclude_filters,exclude_filters=>{setState({exclude_filters:exclude_filters},false);updateTotalContacts()}).init()})}}):null,`<div style="font-size: 14px">${sprintf(__("%s contacts will receive this broadcast.","groundhogg"),bold(formatNumber(getState().totalContacts)))}</div>`,State.when==="later"?Fragment([Div({className:"display-flex column gap-5"},[`<p>Which contacts should be included at the time of sending?</p>`,Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="fixed",onChange:e=>{if(e.target.checked){State.set({segment_type:"fixed"})}}}),"<b>Static Segment:</b> <i>Contacts</i> currently <i>within the segment</i>."]),Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="dynamic",onChange:e=>{if(e.target.checked){State.set({segment_type:"dynamic"})}}}),"<b>Dynamic Segment:</b> <i>Contacts within the segment</i> at the time of sending."])])]):null,Button({id:"go-to-review",className:"gh-button primary",disabled:!getState().totalContacts,style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"schedule"})}},sprintf("%s →",__("Schedule","groundhogg")))])}},schedule:{name:__("Schedule","groundhogg"),icon:Dashicon("calendar"),requirements:()=>getObject(),render:()=>{return Fragment([Div({className:"space-between"},[`<p>${__("When do you want the broadcast to go out?","groundhogg")}</p>`,ButtonToggle({id:"send-when",options:[{id:"later",text:"Later"},{id:"now",text:"Now"}],selected:getState().when,onChange:when=>setState({when:when})})]),getState().when==="later"?Div({className:"gh-input-group"},[Input({type:"date",id:"send-date",name:"date",value:getState().date||"",min:date("Y-m-d"),onChange:e=>setState({date:e.target.value})}),Input({type:"time",id:"send-time",name:"time",value:getState().time||"",onChange:e=>setState({time:e.target.value})}),Button({className:"gh-button grey small",disabled:true},wp.date.getSettings().timezone.abbr||wp.date.getSettings().timezone.string||`UTC${wp.date.getSettings().timezone.offsetFormatted}`)]):null,getState().when==="later"?Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-local"><p>${__("Send in the contact's local time?","groundhogg")}</p></label>`,Toggle({id:"send-in-local",checked:getState().send_in_local_time,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>setState({send_in_local_time:e.target.checked})})]):null,"<div><hr></div>",Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-batches"><p>${__("Send in batches?","groundhogg")}</p></label>`,Toggle({id:"send-in-batches",checked:getState().batching,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>{setState({batching:e.target.checked});if(getState().batching){updateDurationEstimate()}}})]),getState().batching?Fragment([Pg({className:"display-flex gap-5 align-center"},["Send",Input({id:"batch-amount",name:"batch_amount",step:getState().batch_amount<=100?10:50,value:getState().batch_amount,onInput:e=>{setState({batch_amount:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"100px",paddingRight:0}}),"emails every",Input({id:"batch-interval-length",name:"batch_interval_length",step:getState().batch_interval==="minutes"?5:1,value:getState().batch_interval_length,onInput:e=>{setState({batch_interval_length:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"60px",paddingRight:0}}),Select({id:"batch-interval",name:"batch_interval",selected:getState().batch_interval,onChange:e=>{setState({batch_interval:e.target.value},false);updateDurationEstimate()},options:{minutes:__("Minutes"),hours:__("Hours"),days:__("Days")}})]),getState().duration_estimate?Span({className:"pill yellow"},sprintf(__("It will take at least %s to send to %s contacts.","groundhogg"),bold(getState().duration_estimate),formatNumber(getState().totalContacts))):Span({},__("Estimating...","groundhogg"))]):null,"<div><hr></div>",Button({id:"go-to-contacts",className:"gh-button primary",disabled:getState().when==="later"&&!isInTheFuture(`${getState().date} ${getState().time}`),style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"review"})}},sprintf("%s →",__("Review","groundhogg")))])}},review:{name:"Review",icon:Dashicon("thumbs-up"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date)&&getState().totalContacts,render:()=>{let preview;if(getState().when==="now"){preview=sprintf(__("Send %1$s to %2$s contacts <b>now</b>!","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)))}else{preview=sprintf(__("Send %1$s to %2$s contacts on %3$s.","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)),formatDateTime(`${getState().date} ${getState().time}`))}return Fragment([`<p>${preview}</p>`,getObject()?EmailPreview():null,Button({id:"confirm-and-schedule",className:"gh-button primary medium",onClick:e=>{e.target.innerHTML=`<span class="gh-spinner"></span>`;const{when:when="now",date:date="",time:time="",send_in_local_time:send_in_local_time=false,campaigns:campaigns=[],segment_type:segment_type="fixed",batching:batching=false,batch_interval,batch_interval_length,batch_amount}=getState();post(routes.v4.broadcasts,{object_id:getObject().ID,object_type:"email",query:getQuery(),date:date,time:time,send_now:when==="now",send_in_local_time:send_in_local_time,campaigns:campaigns.map(({ID})=>ID),segment_type:segment_type,batching:batching,batch_interval:batch_interval,batch_interval_length:batch_interval_length,batch_amount:batch_amount}).then(r=>{setState({step:"scheduled",broadcast:r.item})}).catch(err=>{dialog({message:err.message,type:"error"});console.log(err);switch(err.code){case"invalid_date":setState({step:"schedule"});break;default:setState({});break}})}},__("Confirm and schedule!","groundhogg"))])}},scheduled:{name:__("Scheduled"),icon:Dashicon("megaphone"),requirements:()=>getState().broadcast,render:()=>{return Fragment([`<p>${__("🎉 Your broadcast is being scheduled in the background!","groundhogg")}</p>`,Button({id:"re-schedule",className:"gh-button primary",style:{alignSelf:"flex-start"},onClick:e=>{setState({...initialState})}},sprintf("← %s",__("Schedule another broadcast","groundhogg")))])}}};const getSteps=()=>{const merged={};const overrides=getState().steps;for(let step in Steps){if(overrides.hasOwnProperty(step)){merged[step]={...Steps[step],...overrides[step]}}else{merged[step]=Steps[step]}}return merged};const BroadcastScheduler=()=>{const order=["object","campaigns","contacts","schedule","review","scheduled"];return Div({id:"broadcast-scheduler",className:"display-flex column gap-10",style:{width:"500px",maxWidth:"100%"}},[getState().step!=="scheduled"?Div({className:"gh-step-nav",style:{marginBottom:"20px"}},[...order.map(step=>Button({id:`select-${step}`,className:`gh-button icon ${getState().step===step?"primary":"secondary"}`,disabled:!getSteps()[step].requirements(),onClick:e=>{setState({step:step})}},getSteps()[step].icon)).reduce((steps,step,i)=>{if(i>0){steps.push(makeEl("hr",{className:"gh-step-nav-join"}))}steps.push(step);return steps},[])]):null,getSteps()[getState().step].render({getState:getState,getObject:getObject,setState:setState,getQuery:getQuery})])};Groundhogg.BroadcastScheduler=(newState={})=>{SearchesStore.maybeFetchItems();State.reset();setState({...newState},false);if(getState().searchMethod!=="filters"){updateTotalContacts(false)}return BroadcastScheduler()};Groundhogg.SendBroadcast=(selector,{email:email=false,...rest}={},{onScheduled:onScheduled=()=>{}})=>{document.querySelector(selector).append(Groundhogg.BroadcastScheduler({object:email,onScheduled:onScheduled}))};$(()=>{$("#gh-schedule-broadcast").on("click",e=>{e.preventDefault();Modal({},()=>Groundhogg.BroadcastScheduler())});if(typeof GroundhoggNewBroadcast!=="undefined"){document.getElementById("gh-broadcast-form-inline").append(Groundhogg.BroadcastScheduler({object:GroundhoggNewBroadcast.email,onScheduled:()=>{window.location.href=adminPageURL("gh_broadcasts",{status:"scheduled"})}}))}})})(jQuery); -
groundhogg/tags/4.1.2.1/groundhogg.php
r3289364 r3293513 4 4 * Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash 5 5 * Description: CRM and marketing automation for WordPress 6 * Version: 4.1.2 6 * Version: 4.1.2.1 7 7 * Author: Groundhogg Inc. 8 8 * Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash … … 25 25 } 26 26 27 define( 'GROUNDHOGG_VERSION', '4.1.2 ' );28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.1. 1.2' );27 define( 'GROUNDHOGG_VERSION', '4.1.2.1' ); 28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.1.2' ); 29 29 30 30 define( 'GROUNDHOGG__FILE__', __FILE__ ); -
groundhogg/tags/4.1.2.1/includes/classes/step.php
r3289364 r3293513 286 286 287 287 return array_filter( $steps, function ( Step $step ) { 288 return str_starts_with( $step->branch, "$this->ID" ); 288 289 if ( $this->is_benchmark() ){ 290 return $step->branch === "$this->ID"; 291 } 292 293 return str_starts_with( $step->branch, "$this->ID-" ); 289 294 } ); 290 295 -
groundhogg/trunk/README.txt
r3289364 r3293513 7 7 Tested up to: 6.8 8 8 Requires PHP: 7.1 9 Stable tag: 4.1.2 9 Stable tag: 4.1.2.1 10 10 License: GPLv3 11 11 License URI: https://www.gnu.org/licenses/gpl.md … … 350 350 351 351 == Changelog == 352 353 = 4.1.2.1 (2025-05-14) = 354 * TWEAKED Changed "Fixed Segment" to "Static Segment". 355 * FIXED Contact activity timeline not loading in some cases if related resource was deleted. 356 * FIXED The same flow step appearing in multiple different trigger branches if the trigger IDs start with the same number. 352 357 353 358 = 4.1.2 (2025-05-06) = -
groundhogg/trunk/assets/js/admin/contacts/contact-editor.js
r3264477 r3293513 1153 1153 ] 1154 1154 1155 Promise.all(promises). then(() => {1155 Promise.all(promises).catch(err => {}).finally(() => { 1156 1156 $el.html(this.render(activities)) 1157 1157 this.onMount() 1158 }).catch(e => {1159 // Something went wrong1160 console.log(e)1161 1158 }) 1162 1159 -
groundhogg/trunk/assets/js/admin/contacts/contact-editor.min.js
r3264477 r3293513 121 121 <ul id="activity-timeline"> 122 122 ${activities.map(a=>{try{return this.renderActivity(a)}catch(e){return""}}).join("")} 123 </ul>`},onMount(){$(".event-queue-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventQueue.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Now")},{key:"cancel",text:`<span class="gh-text danger">${__("Cancel")}</span>`}],onSelect:key=>{switch(key){case"cancel":patch(`${EventQueue.route}/${event.ID}/cancel`).then(()=>{EventQueue.items.splice(EventQueue.items.findIndex(e=>e.ID===event.ID),1);dialog({message:__("Event cancelled","groundhogg")});this.needsRefresh()});break;case"execute":patch(`${EventQueue.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})});$(".event-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventsStore.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Again")}],onSelect:key=>{switch(key){case"execute":patch(`${EventsStore.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})})},mount(selector,activities,{needsRefresh:needsRefresh=()=>{}}){this.needsRefresh=needsRefresh;const $el=$(selector);if(!activities.length){$el.html(`<div class="align-center-space-between" style="margin: 20px"><span class="pill orange">${__("No activity found.","groundhogg")}</span></div>`);return}let funnelIds=activities.reduce((arr,e)=>{let funnelId=parseInt(e.data?.funnel_id||e.form?.data?.funnel_id);if(funnelId>1){if(!arr.includes(funnelId)){arr.push(funnelId)}}return arr},[]);let emailIds=activities.reduce((arr,e)=>{let emailId=parseInt(e.data?.email_id);if(emailId>1){if(!arr.includes(emailId)){arr.push(emailId)}}return arr},[]);activities.filter(a=>a.type==="event"&&a.data.event_type==2).forEach(a=>BroadcastsStore.itemsFetched([a.broadcast]));let promises=[...activities.filter(a=>a.type==="activity"&&this.types[a.data.activity_type]?.hasOwnProperty("preload")).map(a=>this.types[a.data.activity_type]?.preload(a)),funnelIds.length&&!FunnelsStore.hasItems(funnelIds)?FunnelsStore.maybeFetchItems(funnelIds):null,emailIds.length&&!EmailsStore.hasItems(emailIds)?EmailsStore.maybeFetchItems(emailIds):null];Promise.all(promises). then(()=>{$el.html(this.render(activities));this.onMount()}).catch(e=>{console.log(e)})}};const otherContactStuff=()=>{let activeTab=editor.default_tab??"activity";const tabs=[{id:"activity",name:__("Activity"),render:()=>{return`123 </ul>`},onMount(){$(".event-queue-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventQueue.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Now")},{key:"cancel",text:`<span class="gh-text danger">${__("Cancel")}</span>`}],onSelect:key=>{switch(key){case"cancel":patch(`${EventQueue.route}/${event.ID}/cancel`).then(()=>{EventQueue.items.splice(EventQueue.items.findIndex(e=>e.ID===event.ID),1);dialog({message:__("Event cancelled","groundhogg")});this.needsRefresh()});break;case"execute":patch(`${EventQueue.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})});$(".event-more").on("click",e=>{let eventId=e.currentTarget.dataset.event;const event=EventsStore.get(eventId);moreMenu(e.currentTarget,{items:[{key:"execute",text:__("Run Again")}],onSelect:key=>{switch(key){case"execute":patch(`${EventsStore.route}/${event.ID}/execute`).then(()=>{dialog({message:__("Event rescheduled","groundhogg")});this.needsRefresh()});break}}})})},mount(selector,activities,{needsRefresh:needsRefresh=()=>{}}){this.needsRefresh=needsRefresh;const $el=$(selector);if(!activities.length){$el.html(`<div class="align-center-space-between" style="margin: 20px"><span class="pill orange">${__("No activity found.","groundhogg")}</span></div>`);return}let funnelIds=activities.reduce((arr,e)=>{let funnelId=parseInt(e.data?.funnel_id||e.form?.data?.funnel_id);if(funnelId>1){if(!arr.includes(funnelId)){arr.push(funnelId)}}return arr},[]);let emailIds=activities.reduce((arr,e)=>{let emailId=parseInt(e.data?.email_id);if(emailId>1){if(!arr.includes(emailId)){arr.push(emailId)}}return arr},[]);activities.filter(a=>a.type==="event"&&a.data.event_type==2).forEach(a=>BroadcastsStore.itemsFetched([a.broadcast]));let promises=[...activities.filter(a=>a.type==="activity"&&this.types[a.data.activity_type]?.hasOwnProperty("preload")).map(a=>this.types[a.data.activity_type]?.preload(a)),funnelIds.length&&!FunnelsStore.hasItems(funnelIds)?FunnelsStore.maybeFetchItems(funnelIds):null,emailIds.length&&!EmailsStore.hasItems(emailIds)?EmailsStore.maybeFetchItems(emailIds):null];Promise.all(promises).catch(err=>{}).finally(()=>{$el.html(this.render(activities));this.onMount()})}};const otherContactStuff=()=>{let activeTab=editor.default_tab??"activity";const tabs=[{id:"activity",name:__("Activity"),render:()=>{return` 124 124 <div class="gh-panel top-left-square"> 125 125 <div class="inside"> -
groundhogg/trunk/assets/js/admin/data.js
r3264477 r3293513 27 27 28 28 if (!response.ok) { 29 console.log(json)30 29 throw new ApiError(json.message, json.code) 31 30 } -
groundhogg/trunk/assets/js/admin/data.min.js
r3264477 r3293513 1 (function($){function ApiError(message,code="error"){this.name="ApiError";this.message=message;this.code=code}ApiError.prototype=Error.prototype;async function apiGet(route,params={},opts={}){const response=await fetch(route+"?"+$.param(params),{headers:{"X-WP-Nonce":wpApiSettings.nonce},...opts});let json=await response.json();if(!response.ok){ console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiPost(url="",data={},opts={}){const response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data),...opts});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiPatch(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"PATCH",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiDelete(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"DELETE",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}const apiPostFormData=async(url,data,opts={})=>{const response=await fetch(url,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":wpApiSettings.nonce},body:data,...opts});return response.json()};async function adminAjax(data={},opts={}){if(!(data instanceof FormData)){const fData=new FormData;for(const key in data){if(data.hasOwnProperty(key)){fData.append(key,data[key])}}data=fData}let ajaxUrl=opts.url??ajaxurl;delete opts.url;data.append("gh_admin_ajax_nonce",Groundhogg.nonces._adminajax);const response=await fetch(ajaxUrl,{method:"POST",credentials:"same-origin",body:data,...opts});return response.json()}const ObjectStore=(route,extra={})=>({primaryKey:"ID",getItemFromResponse:r=>r.item,getItemsFromResponse:r=>r.items??[],getTotalItemsFromResponse:r=>r.total_items,items:[],total_items:0,route:route,cache:{},getResultsFromCache(query={}){let[results=[],totalItems=0]=this.cache[JSON.stringify(query)]??[];this.total_items=totalItems;return results.map(id=>this.get(id))},clearResultsCache(key=""){this.cache={}},setInResultsCache(query,results=[],totalItems=0){this.cache[JSON.stringify(query)]=[results.map(item=>item[this.primaryKey]),totalItems]},hasCachedResults(query){return JSON.stringify(query)in this.cache},get(id){return this.items.find(item=>item[this.primaryKey]==id)},getItems(){return this.items},has(id){return this.hasItem(id)},hasItem(id){return this.items.some(item=>item[this.primaryKey]==id)},hasItems(itemIds=[]){if(!itemIds||itemIds.length===0){return this.items.length>0}for(let i=0;i<itemIds.length;i++){const itemId=itemIds[i];if(!this.items.find(item=>{return item[this.primaryKey]==itemId})){return false}}return true},getTotalItems(){return this.total_items},itemsFetched(items){if(!Array.isArray(items)){return}this.items=[...items,...this.items.filter(item=>!items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))]},clearItems(){this.items=[]},find(f=()=>{}){return this.items.find(f)},filter(f=()=>{}){return this.items.filter(f)},async fetchItems(params,opts={}){if(this.hasCachedResults(params)){return this.getResultsFromCache(params)}return apiGet(this.route,params,opts).then(r=>{this.total_items=this.getTotalItemsFromResponse(r);return this.getItemsFromResponse(r)}).then(items=>{this.itemsFetched(items);this.setInResultsCache(params,items,this.total_items);return items})},async maybeFetchItems(ids=[],opts={}){const{param:param=this.primaryKey,...otherOpts}=opts;if((!ids||ids.length===0)&&this.hasItems()){return this.items}if(ids&&ids.length>0&&ids.every(id=>this.hasItem(id))){return ids.map(id=>this.get(id))}if(!ids||!ids.length){return this.fetchItems({},opts)}let missingIds=ids.filter(id=>!this.hasItem(id));const params={[param]:missingIds,limit:missingIds.length};return this.fetchItems(params,otherOpts)},async fetchItem(id,opts={}){return apiGet(`${this.route}/${id}`,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async maybeFetchItem(id,opts={}){if(this.hasItem(id)){return this.get(id)}return this.fetchItem(id,opts)},async create(...args){return this.post(...args)},async createMany(...args){return this.postMany(...args)},async post(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.clearResultsCache();this.itemsFetched([item]);return item})},async postMany(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.clearResultsCache();this.itemsFetched(items);return items})},async update(...args){return this.patch(...args)},async patch(id,data,opts={}){return apiPatch(`${this.route}/${id}`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMany(items,opts={}){return apiPatch(`${this.route}`,items,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},async duplicate(id,data,opts={}){return apiPost(`${this.route}/${id}/duplicate`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMeta(id,data,opts={}){return apiPatch(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async deleteMeta(id,data,opts={}){return apiDelete(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async fetchRelationships(id,{other_type,...rest},opts={}){return apiGet(`${this.route}/${id}/relationships`,{other_type:other_type,...rest},opts).then(r=>this.getItemsFromResponse(r))},async createRelationships(id,data,opts={}){return apiPost(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async deleteRelationships(id,data,opts={}){return apiDelete(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async count(params){return apiGet(`${this.route}`,{count:true,...params}).then(r=>r.total_items)},async delete(id){this.clearResultsCache();if(typeof id=="object"){return this.deleteMany(id)}return apiDelete(`${this.route}/${id}`).then(r=>{this.items=[...this.items.filter(item=>item[this.primaryKey]!=id)];return r})},async deleteMany(query){this.clearResultsCache();return apiDelete(`${this.route}`,query).then(r=>{let items=this.getItemsFromResponse(r);this.items=[...this.items.filter(item=>!items.includes(item[this.primaryKey]))];return r})},...extra});Groundhogg.api.post=apiPost;Groundhogg.api.postFormData=apiPostFormData;Groundhogg.api.get=apiGet;Groundhogg.api.patch=apiPatch;Groundhogg.api.delete=apiDelete;Groundhogg.api.ajax=adminAjax;Groundhogg.api.ApiError=ApiError;Groundhogg.stores={options:{items:{},route:Groundhogg.api.routes.v4.options,get(opt,_default=false){if(Array.isArray(opt)){let opts={};opt.forEach(_opt=>opts[_opt]=this.items[_opt]);return opts}return this.items[opt]?this.items[opt]:_default},fetch(data=[],opts={}){let req={};data.forEach(opt=>req[opt]=1);return apiGet(this.route,req,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},post(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},patch(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},delete(data={},opts={}){return apiDelete(this.route,data,opts).then(r=>{data.forEach(opt=>{delete this.items[opt]})})}},tags:ObjectStore(Groundhogg.api.routes.v4.tags,{limit:100,offset:0,async validate(maybeTags){var self=this;return await apiPost(`${this.route}/validate`,maybeTags).then(r=>{if(!r.items){return[]}self.items=[...r.items,...self.items.filter(item=>!r.items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))];return r.items})},preloadTags(){var self=this;return apiGet(this.route,{limit:self.limit,offet:self.offset}).then(data=>{if(!data.items){return}Object.assign(self.items,data.items);if(data.items.length==self.limit){self.offset+=self.limit;self.preloadTags()}})}}),forms:ObjectStore(Groundhogg.api.routes.v4.forms),contacts:ObjectStore(Groundhogg.api.routes.v4.contacts,{async fetchFiles(id,opts={}){return apiGet(`${this.route}/${id}/files`,{},opts).then(r=>this.getItemsFromResponse(r))}}),events:ObjectStore(Groundhogg.api.routes.v4.events),event_queue:ObjectStore(Groundhogg.api.routes.v4.event_queue),page_visits:ObjectStore(Groundhogg.api.routes.v4.page_visits),activity:ObjectStore(Groundhogg.api.routes.v4.activity),campaigns:ObjectStore(Groundhogg.api.routes.v4.campaigns),submissions:ObjectStore(Groundhogg.api.routes.v4.submissions),funnels:ObjectStore(Groundhogg.api.routes.v4.funnels,{async addContacts({query,funnel_id,step_id,...rest},opts={}){return apiPost(`${this.route}/${funnel_id}/start`,{query:query,step_id:step_id,funnel_id:funnel_id,...rest},opts).then(d=>d.added)},async commit(id,data,opts={}){return apiPost(`${this.route}/${id}/commit`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},isStartingStep(funnelId,stepId,checkEdited=false){return!this.getPrecedingSteps(funnelId,stepId,checkEdited).find(_step=>_step.data.step_group=="action")},getSteps(funnelId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;return checkEdited&&funnel.meta.edited?funnel.meta.edited.steps:funnel.steps},getFunnelAndStep(funnelId,stepId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;const step=checkEdited&&funnel.meta.edited?funnel.meta.edited.steps.find(s=>s.ID==stepId):funnel.steps.find(s=>s.ID==stepId);return{funnel:funnel,step:step}},getProceedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order>step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)},getPrecedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order<step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)}}),emails:ObjectStore(Groundhogg.api.routes.v4.emails,{send(id,data){return apiPost(`${this.route}/${id}/send`,data)}}),broadcasts:ObjectStore(Groundhogg.api.routes.v4.broadcasts),notes:ObjectStore(Groundhogg.api.routes.v4.notes),replies:ObjectStore(Groundhogg.api.routes.v4.notes),tasks:ObjectStore(Groundhogg.api.routes.v4.tasks,{complete(id){return apiPatch(`${this.route}/${id}/complete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},completeMany(ids){return apiPatch(`${this.route}/complete`,ids).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},incomplete(id){return apiPatch(`${this.route}/${id}/incomplete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})}}),searches:ObjectStore(Groundhogg.api.routes.v4.searches,{primaryKey:"id"}),posts:ObjectStore(Groundhogg.api.routes.posts,{primaryKey:"id"}),email_log:ObjectStore(Groundhogg.api.routes.v4.email_log)};Groundhogg.createStore=(id,route="",extra={})=>{const store=ObjectStore(route,extra);Groundhogg.stores[id]=store;return store};const createState=(initialState={})=>new Proxy({initial:{...initialState},state:{...initialState},set(newState){this.state={...this.state,...newState}},clear(){this.state={}},reset(){this.set({...this.initial})},get(key=""){if(key){return this.state[key]}return this.state},has(key=""){if(key){return key in this.state}return Object.keys(this.state).length>0}},{set(manager,key,val){if(key==="state"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"state"),key,val)},get(manager,key,receiver){if(key==="state"){return Reflect.get(manager,key)}let state=Reflect.get(manager,"state");if(Reflect.has(state,key)){return Reflect.get(state,key)}return Reflect.get(manager,key)}});const stateMap=new WeakMap;function useState(initialState,caller=false){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,createState(initialState))}return stateMap.get(caller)}function bindState(state,caller){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,state)}}const createRegistry=(initialItems={})=>new Proxy({items:{...initialItems},add(key,newItem){this.items[key]=newItem},clear(){this.items={}},get(key=""){if(key){return this.items[key]}return this.items},keys(){return Object.keys(this.items)},filter(func){return this.keys().filter(key=>func(this[key],key))},map(func){return this.keys().map(key=>func(this[key],key))},has(key=""){if(key){return key in this.items}return Object.keys(this.items).length>0}},{set(manager,key,val){if(key==="items"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"items"),key,val)},get(manager,key,receiver){if(key==="items"){return Reflect.get(manager,key)}let items=Reflect.get(manager,"items");if(Reflect.has(items,key)){return Reflect.get(items,key)}return Reflect.get(manager,key)}});Groundhogg.createState=createState;Groundhogg.useState=useState;Groundhogg.bindState=bindState;Groundhogg.createRegistry=createRegistry})(jQuery);1 (function($){function ApiError(message,code="error"){this.name="ApiError";this.message=message;this.code=code}ApiError.prototype=Error.prototype;async function apiGet(route,params={},opts={}){const response=await fetch(route+"?"+$.param(params),{headers:{"X-WP-Nonce":wpApiSettings.nonce},...opts});let json=await response.json();if(!response.ok){throw new ApiError(json.message,json.code)}return json}async function apiPost(url="",data={},opts={}){const response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data),...opts});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiPatch(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"PATCH",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}async function apiDelete(url="",data={},opts={}){const response=await fetch(url,{...opts,method:"DELETE",headers:{"Content-Type":"application/json","X-WP-Nonce":wpApiSettings.nonce},body:JSON.stringify(data)});let json=await response.json();if(!response.ok){console.log(json);throw new ApiError(json.message,json.code)}return json}const apiPostFormData=async(url,data,opts={})=>{const response=await fetch(url,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":wpApiSettings.nonce},body:data,...opts});return response.json()};async function adminAjax(data={},opts={}){if(!(data instanceof FormData)){const fData=new FormData;for(const key in data){if(data.hasOwnProperty(key)){fData.append(key,data[key])}}data=fData}let ajaxUrl=opts.url??ajaxurl;delete opts.url;data.append("gh_admin_ajax_nonce",Groundhogg.nonces._adminajax);const response=await fetch(ajaxUrl,{method:"POST",credentials:"same-origin",body:data,...opts});return response.json()}const ObjectStore=(route,extra={})=>({primaryKey:"ID",getItemFromResponse:r=>r.item,getItemsFromResponse:r=>r.items??[],getTotalItemsFromResponse:r=>r.total_items,items:[],total_items:0,route:route,cache:{},getResultsFromCache(query={}){let[results=[],totalItems=0]=this.cache[JSON.stringify(query)]??[];this.total_items=totalItems;return results.map(id=>this.get(id))},clearResultsCache(key=""){this.cache={}},setInResultsCache(query,results=[],totalItems=0){this.cache[JSON.stringify(query)]=[results.map(item=>item[this.primaryKey]),totalItems]},hasCachedResults(query){return JSON.stringify(query)in this.cache},get(id){return this.items.find(item=>item[this.primaryKey]==id)},getItems(){return this.items},has(id){return this.hasItem(id)},hasItem(id){return this.items.some(item=>item[this.primaryKey]==id)},hasItems(itemIds=[]){if(!itemIds||itemIds.length===0){return this.items.length>0}for(let i=0;i<itemIds.length;i++){const itemId=itemIds[i];if(!this.items.find(item=>{return item[this.primaryKey]==itemId})){return false}}return true},getTotalItems(){return this.total_items},itemsFetched(items){if(!Array.isArray(items)){return}this.items=[...items,...this.items.filter(item=>!items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))]},clearItems(){this.items=[]},find(f=()=>{}){return this.items.find(f)},filter(f=()=>{}){return this.items.filter(f)},async fetchItems(params,opts={}){if(this.hasCachedResults(params)){return this.getResultsFromCache(params)}return apiGet(this.route,params,opts).then(r=>{this.total_items=this.getTotalItemsFromResponse(r);return this.getItemsFromResponse(r)}).then(items=>{this.itemsFetched(items);this.setInResultsCache(params,items,this.total_items);return items})},async maybeFetchItems(ids=[],opts={}){const{param:param=this.primaryKey,...otherOpts}=opts;if((!ids||ids.length===0)&&this.hasItems()){return this.items}if(ids&&ids.length>0&&ids.every(id=>this.hasItem(id))){return ids.map(id=>this.get(id))}if(!ids||!ids.length){return this.fetchItems({},opts)}let missingIds=ids.filter(id=>!this.hasItem(id));const params={[param]:missingIds,limit:missingIds.length};return this.fetchItems(params,otherOpts)},async fetchItem(id,opts={}){return apiGet(`${this.route}/${id}`,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async maybeFetchItem(id,opts={}){if(this.hasItem(id)){return this.get(id)}return this.fetchItem(id,opts)},async create(...args){return this.post(...args)},async createMany(...args){return this.postMany(...args)},async post(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.clearResultsCache();this.itemsFetched([item]);return item})},async postMany(data,opts={}){return apiPost(this.route,data,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.clearResultsCache();this.itemsFetched(items);return items})},async update(...args){return this.patch(...args)},async patch(id,data,opts={}){return apiPatch(`${this.route}/${id}`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMany(items,opts={}){return apiPatch(`${this.route}`,items,opts).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},async duplicate(id,data,opts={}){return apiPost(`${this.route}/${id}/duplicate`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async patchMeta(id,data,opts={}){return apiPatch(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async deleteMeta(id,data,opts={}){return apiDelete(`${this.route}/${id}/meta`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},async fetchRelationships(id,{other_type,...rest},opts={}){return apiGet(`${this.route}/${id}/relationships`,{other_type:other_type,...rest},opts).then(r=>this.getItemsFromResponse(r))},async createRelationships(id,data,opts={}){return apiPost(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async deleteRelationships(id,data,opts={}){return apiDelete(`${this.route}/${id}/relationships`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);this.clearResultsCache();return item})},async count(params){return apiGet(`${this.route}`,{count:true,...params}).then(r=>r.total_items)},async delete(id){this.clearResultsCache();if(typeof id=="object"){return this.deleteMany(id)}return apiDelete(`${this.route}/${id}`).then(r=>{this.items=[...this.items.filter(item=>item[this.primaryKey]!=id)];return r})},async deleteMany(query){this.clearResultsCache();return apiDelete(`${this.route}`,query).then(r=>{let items=this.getItemsFromResponse(r);this.items=[...this.items.filter(item=>!items.includes(item[this.primaryKey]))];return r})},...extra});Groundhogg.api.post=apiPost;Groundhogg.api.postFormData=apiPostFormData;Groundhogg.api.get=apiGet;Groundhogg.api.patch=apiPatch;Groundhogg.api.delete=apiDelete;Groundhogg.api.ajax=adminAjax;Groundhogg.api.ApiError=ApiError;Groundhogg.stores={options:{items:{},route:Groundhogg.api.routes.v4.options,get(opt,_default=false){if(Array.isArray(opt)){let opts={};opt.forEach(_opt=>opts[_opt]=this.items[_opt]);return opts}return this.items[opt]?this.items[opt]:_default},fetch(data=[],opts={}){let req={};data.forEach(opt=>req[opt]=1);return apiGet(this.route,req,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},post(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},patch(data={},opts={}){return apiPatch(this.route,data,opts).then(r=>{this.items={...this.items,...r.items};return r.items})},delete(data={},opts={}){return apiDelete(this.route,data,opts).then(r=>{data.forEach(opt=>{delete this.items[opt]})})}},tags:ObjectStore(Groundhogg.api.routes.v4.tags,{limit:100,offset:0,async validate(maybeTags){var self=this;return await apiPost(`${this.route}/validate`,maybeTags).then(r=>{if(!r.items){return[]}self.items=[...r.items,...self.items.filter(item=>!r.items.find(_item=>_item[this.primaryKey]==item[this.primaryKey]))];return r.items})},preloadTags(){var self=this;return apiGet(this.route,{limit:self.limit,offet:self.offset}).then(data=>{if(!data.items){return}Object.assign(self.items,data.items);if(data.items.length==self.limit){self.offset+=self.limit;self.preloadTags()}})}}),forms:ObjectStore(Groundhogg.api.routes.v4.forms),contacts:ObjectStore(Groundhogg.api.routes.v4.contacts,{async fetchFiles(id,opts={}){return apiGet(`${this.route}/${id}/files`,{},opts).then(r=>this.getItemsFromResponse(r))}}),events:ObjectStore(Groundhogg.api.routes.v4.events),event_queue:ObjectStore(Groundhogg.api.routes.v4.event_queue),page_visits:ObjectStore(Groundhogg.api.routes.v4.page_visits),activity:ObjectStore(Groundhogg.api.routes.v4.activity),campaigns:ObjectStore(Groundhogg.api.routes.v4.campaigns),submissions:ObjectStore(Groundhogg.api.routes.v4.submissions),funnels:ObjectStore(Groundhogg.api.routes.v4.funnels,{async addContacts({query,funnel_id,step_id,...rest},opts={}){return apiPost(`${this.route}/${funnel_id}/start`,{query:query,step_id:step_id,funnel_id:funnel_id,...rest},opts).then(d=>d.added)},async commit(id,data,opts={}){return apiPost(`${this.route}/${id}/commit`,data,opts).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},isStartingStep(funnelId,stepId,checkEdited=false){return!this.getPrecedingSteps(funnelId,stepId,checkEdited).find(_step=>_step.data.step_group=="action")},getSteps(funnelId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;return checkEdited&&funnel.meta.edited?funnel.meta.edited.steps:funnel.steps},getFunnelAndStep(funnelId,stepId,checkEdited=false){const funnel=funnelId?this.items.find(f=>f.ID==funnelId):this.item;const step=checkEdited&&funnel.meta.edited?funnel.meta.edited.steps.find(s=>s.ID==stepId):funnel.steps.find(s=>s.ID==stepId);return{funnel:funnel,step:step}},getProceedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order>step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)},getPrecedingSteps(funnelId,stepId,checkEdited=false){const{step,funnel}=this.getFunnelAndStep(funnelId,stepId,checkEdited);return funnel.steps.filter(_step=>_step.data.step_order<step.data.step_order).sort((a,b)=>a.data.step_order-b.data.step_order)}}),emails:ObjectStore(Groundhogg.api.routes.v4.emails,{send(id,data){return apiPost(`${this.route}/${id}/send`,data)}}),broadcasts:ObjectStore(Groundhogg.api.routes.v4.broadcasts),notes:ObjectStore(Groundhogg.api.routes.v4.notes),replies:ObjectStore(Groundhogg.api.routes.v4.notes),tasks:ObjectStore(Groundhogg.api.routes.v4.tasks,{complete(id){return apiPatch(`${this.route}/${id}/complete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})},completeMany(ids){return apiPatch(`${this.route}/complete`,ids).then(r=>this.getItemsFromResponse(r)).then(items=>{this.itemsFetched(items);return items})},incomplete(id){return apiPatch(`${this.route}/${id}/incomplete`).then(r=>this.getItemFromResponse(r)).then(item=>{this.itemsFetched([item]);return item})}}),searches:ObjectStore(Groundhogg.api.routes.v4.searches,{primaryKey:"id"}),posts:ObjectStore(Groundhogg.api.routes.posts,{primaryKey:"id"}),email_log:ObjectStore(Groundhogg.api.routes.v4.email_log)};Groundhogg.createStore=(id,route="",extra={})=>{const store=ObjectStore(route,extra);Groundhogg.stores[id]=store;return store};const createState=(initialState={})=>new Proxy({initial:{...initialState},state:{...initialState},set(newState){this.state={...this.state,...newState}},clear(){this.state={}},reset(){this.set({...this.initial})},get(key=""){if(key){return this.state[key]}return this.state},has(key=""){if(key){return key in this.state}return Object.keys(this.state).length>0}},{set(manager,key,val){if(key==="state"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"state"),key,val)},get(manager,key,receiver){if(key==="state"){return Reflect.get(manager,key)}let state=Reflect.get(manager,"state");if(Reflect.has(state,key)){return Reflect.get(state,key)}return Reflect.get(manager,key)}});const stateMap=new WeakMap;function useState(initialState,caller=false){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,createState(initialState))}return stateMap.get(caller)}function bindState(state,caller){if(!caller){caller=useState.caller}if(!stateMap.has(caller)){stateMap.set(caller,state)}}const createRegistry=(initialItems={})=>new Proxy({items:{...initialItems},add(key,newItem){this.items[key]=newItem},clear(){this.items={}},get(key=""){if(key){return this.items[key]}return this.items},keys(){return Object.keys(this.items)},filter(func){return this.keys().filter(key=>func(this[key],key))},map(func){return this.keys().map(key=>func(this[key],key))},has(key=""){if(key){return key in this.items}return Object.keys(this.items).length>0}},{set(manager,key,val){if(key==="items"){return Reflect.set(manager,key,val)}return Reflect.set(Reflect.get(manager,"items"),key,val)},get(manager,key,receiver){if(key==="items"){return Reflect.get(manager,key)}let items=Reflect.get(manager,"items");if(Reflect.has(items,key)){return Reflect.get(items,key)}return Reflect.get(manager,key)}});Groundhogg.createState=createState;Groundhogg.useState=useState;Groundhogg.bindState=bindState;Groundhogg.createRegistry=createRegistry})(jQuery); -
groundhogg/trunk/assets/js/admin/features/send-broadcast.js
r3221208 r3293513 467 467 468 468 }), 469 '<b> FixedSegment:</b> <i>Contacts</i> currently <i>within the segment</i>.',469 '<b>Static Segment:</b> <i>Contacts</i> currently <i>within the segment</i>.', 470 470 ]), 471 471 Label({ -
groundhogg/trunk/assets/js/admin/features/send-broadcast.min.js
r3221208 r3293513 1 (function($){const{adminPageURL,bold,icons,dialog}=Groundhogg.element;const{emails:EmailsStore,searches:SearchesStore,contacts:ContactsStore,campaigns:CampaignsStore}=Groundhogg.stores;const{routes,post,ajax}=Groundhogg.api;const{createFilters}=Groundhogg.filters.functions;const{formatNumber,formatDateTime}=Groundhogg.formatting;const{debounce}=Groundhogg.functions;const{sprintf,__,_x,_n}=wp.i18n;const{isInTheFuture,getDate,date}=wp.date;const{Div,Button,Pg,Modal,Textarea,Select,ItemPicker,Fragment,Input,InputGroup,Iframe,makeEl,ButtonToggle,Label,Span,Toggle,Dashicon}=MakeEl;const initialState={step:"object",steps:[],object:null,when:"later",campaigns:[],searchMethod:"filters",searchMethods:[],totalContacts:0,date:date("Y-m-d"),time:date("H:00:00",getDate().setHours(getDate().getHours()+1)),broadcast:null,segment_type:"fixed",batching:false,batch_interval:"minutes",batch_interval_length:30,batch_amount:100,send_in_local_time:false};const getSearchMethods=()=>{return[...getState().searchMethods??[],{id:"filters",text:__("Search for contacts using filters.","groundhogg"),query:()=>({filters:getState().include_filters,exclude_filters:getState().exclude_filters})},{id:"all-contacts",text:__("All contacts.","groundhogg"),query:()=>({})},{id:"all-my-contacts",text:__("All contacts assigned to me.","groundhogg"),query:()=>({owner_id:Groundhogg.currentUser.ID})},{id:"confirmed-contacts",text:__("All confirmed contacts.","groundhogg"),query:()=>({optin_status:2})},...SearchesStore.getItems().map(({id,name})=>({id:id,text:sprintf(__("Saved search %s","groundhogg"),bold(name)),query:()=>({saved_search:id})}))]};const State=Groundhogg.createState({...initialState});const getQuery=()=>{let query={};const{searchMethod:searchMethod="filters"}=getState();query=getSearchMethods().find(({id})=>id===searchMethod).query();if(getObject()&&getObject().data.message_type!=="transactional"){query.marketable="yes"}return query};const updateTotalContacts=(morph=true)=>{return ContactsStore.count(getQuery()).then(total=>{setState({totalContacts:total},morph)})};const updateDurationEstimate=debounce(()=>ajax({action:"gh_estimate_send_duration",total_contacts:getState().totalContacts,batch_interval:getState().batch_interval,batch_interval_length:getState().batch_interval_length,batch_amount:getState().batch_amount}).then(r=>{const{time}=r.data;setState({duration_estimate:time})}),500);const getState=()=>State;const setState=(newState,morph=true)=>{State.set({...newState});if(morph){try{morphdom(document.getElementById("broadcast-scheduler"),BroadcastScheduler())}catch(e){console.log(e)}}};const getObject=()=>getState().object;const FromPreview=()=>Div({className:"from-preview display-flex gap-20 has-box-shadow"},[makeEl("img",{src:getObject().context.from_avatar,className:"from-avatar",height:40,width:40,style:{borderRadius:"50%"}}),Div({className:"subject-and-from"},[`<h2>${getObject().data.subject}</h2>`,`<span class="from-name">${getObject().context.from_name}</span> <span class="from-email"><${getObject().context.from_email}></span>`])]);const EmailPreview=()=>{return Div({className:"email-preview display-flex column",style:{overflow:"hidden",border:"1px solid #ccc",borderRadius:"5px"}},[FromPreview(),Iframe({id:`broadcast-email-preview`,height:400,style:{width:"100%"}},getObject().context.built)])};const Steps={object:{name:__("Email","groundhogg"),icon:icons.email,requirements:()=>true,render:()=>{return Fragment([Div({className:"display-flex column gap-10"},[`<p>${__("Select an email to send...","groundhogg")}</p>`,Div({className:"display-flex gap-10"},[ItemPicker({id:`broadcast-select-email`,noneSelected:__("Select an email to send...","groundhogg"),selected:getObject()?{id:getObject().ID,text:getObject().data.title}:[],multiple:false,style:{flexGrow:1},fetchOptions:search=>{return EmailsStore.fetchItems({search:search,status:"ready"}).then(emails=>emails.map(({ID,data})=>({id:ID,text:data.title})))},onChange:item=>{if(!item){setState({object:null});return}let email=EmailsStore.get(item.id);setState({object:email,campaigns:email.campaigns})}}),getObject()?Button({id:"go-to-campaigns",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"campaigns"})}},sprintf("%s →",__("Campaigns","groundhogg"))):null])]),getObject()?EmailPreview():null])}},campaigns:{name:__("Campaigns","groundhogg"),requirements:()=>getObject(),icon:Dashicon("flag"),render:()=>{return Fragment([`<p>${__("Use campaigns to organize your broadcasts! Select one or more campaigns...","groundhogg")}</p>`,ItemPicker({id:"broadcast-campaigns",noneSelected:__("Select a campaign...","groundhogg"),tags:true,selected:getState().campaigns.map(({ID,data})=>({id:ID,text:data.name})),fetchOptions:async search=>{let campaigns=await CampaignsStore.fetchItems({search:search,limit:20});return campaigns.map(({ID,data})=>({id:ID,text:data.name}))},createOption:async id=>{let campaign=await CampaignsStore.create({data:{name:id}});return{id:campaign.ID,text:campaign.data.name}},onChange:items=>setState({campaigns:items.map(({id})=>CampaignsStore.get(id))})}),Button({id:"go-to-schedule",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"contacts"})}},sprintf("%s →",__("Contacts","groundhogg")))])}},contacts:{name:__("Contacts","groundhogg"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date),icon:icons.contact,render:()=>{return Fragment([`<p>${__("Select contacts to receive this broadcast...","groundhogg")}</p>`,ItemPicker({id:"select-search-method",multiple:false,selected:getSearchMethods().find(({id})=>id===getState().searchMethod),fetchOptions:async search=>{return getSearchMethods().filter(({text})=>text.match(new RegExp(search,"i")))},onChange:item=>{if(!item){setState({searchMethod:"filters"});updateTotalContacts();return}let{id}=item;setState({searchMethod:id});updateTotalContacts()}}),getState().searchMethod==="filters"?Div({id:"broadcast-include-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-include-filters",getState().include_filters,include_filters=>{setState({include_filters:include_filters},false);updateTotalContacts()}).init()})}}):null,getState().searchMethod==="filters"?Div({id:"broadcast-exclude-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-exclude-filters",getState().exclude_filters,exclude_filters=>{setState({exclude_filters:exclude_filters},false);updateTotalContacts()}).init()})}}):null,`<div style="font-size: 14px">${sprintf(__("%s contacts will receive this broadcast.","groundhogg"),bold(formatNumber(getState().totalContacts)))}</div>`,State.when==="later"?Fragment([Div({className:"display-flex column gap-5"},[`<p>Which contacts should be included at the time of sending?</p>`,Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="fixed",onChange:e=>{if(e.target.checked){State.set({segment_type:"fixed"})}}}),"<b> FixedSegment:</b> <i>Contacts</i> currently <i>within the segment</i>."]),Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="dynamic",onChange:e=>{if(e.target.checked){State.set({segment_type:"dynamic"})}}}),"<b>Dynamic Segment:</b> <i>Contacts within the segment</i> at the time of sending."])])]):null,Button({id:"go-to-review",className:"gh-button primary",disabled:!getState().totalContacts,style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"schedule"})}},sprintf("%s →",__("Schedule","groundhogg")))])}},schedule:{name:__("Schedule","groundhogg"),icon:Dashicon("calendar"),requirements:()=>getObject(),render:()=>{return Fragment([Div({className:"space-between"},[`<p>${__("When do you want the broadcast to go out?","groundhogg")}</p>`,ButtonToggle({id:"send-when",options:[{id:"later",text:"Later"},{id:"now",text:"Now"}],selected:getState().when,onChange:when=>setState({when:when})})]),getState().when==="later"?Div({className:"gh-input-group"},[Input({type:"date",id:"send-date",name:"date",value:getState().date||"",min:date("Y-m-d"),onChange:e=>setState({date:e.target.value})}),Input({type:"time",id:"send-time",name:"time",value:getState().time||"",onChange:e=>setState({time:e.target.value})}),Button({className:"gh-button grey small",disabled:true},wp.date.getSettings().timezone.abbr||wp.date.getSettings().timezone.string||`UTC${wp.date.getSettings().timezone.offsetFormatted}`)]):null,getState().when==="later"?Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-local"><p>${__("Send in the contact's local time?","groundhogg")}</p></label>`,Toggle({id:"send-in-local",checked:getState().send_in_local_time,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>setState({send_in_local_time:e.target.checked})})]):null,"<div><hr></div>",Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-batches"><p>${__("Send in batches?","groundhogg")}</p></label>`,Toggle({id:"send-in-batches",checked:getState().batching,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>{setState({batching:e.target.checked});if(getState().batching){updateDurationEstimate()}}})]),getState().batching?Fragment([Pg({className:"display-flex gap-5 align-center"},["Send",Input({id:"batch-amount",name:"batch_amount",step:getState().batch_amount<=100?10:50,value:getState().batch_amount,onInput:e=>{setState({batch_amount:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"100px",paddingRight:0}}),"emails every",Input({id:"batch-interval-length",name:"batch_interval_length",step:getState().batch_interval==="minutes"?5:1,value:getState().batch_interval_length,onInput:e=>{setState({batch_interval_length:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"60px",paddingRight:0}}),Select({id:"batch-interval",name:"batch_interval",selected:getState().batch_interval,onChange:e=>{setState({batch_interval:e.target.value},false);updateDurationEstimate()},options:{minutes:__("Minutes"),hours:__("Hours"),days:__("Days")}})]),getState().duration_estimate?Span({className:"pill yellow"},sprintf(__("It will take at least %s to send to %s contacts.","groundhogg"),bold(getState().duration_estimate),formatNumber(getState().totalContacts))):Span({},__("Estimating...","groundhogg"))]):null,"<div><hr></div>",Button({id:"go-to-contacts",className:"gh-button primary",disabled:getState().when==="later"&&!isInTheFuture(`${getState().date} ${getState().time}`),style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"review"})}},sprintf("%s →",__("Review","groundhogg")))])}},review:{name:"Review",icon:Dashicon("thumbs-up"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date)&&getState().totalContacts,render:()=>{let preview;if(getState().when==="now"){preview=sprintf(__("Send %1$s to %2$s contacts <b>now</b>!","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)))}else{preview=sprintf(__("Send %1$s to %2$s contacts on %3$s.","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)),formatDateTime(`${getState().date} ${getState().time}`))}return Fragment([`<p>${preview}</p>`,getObject()?EmailPreview():null,Button({id:"confirm-and-schedule",className:"gh-button primary medium",onClick:e=>{e.target.innerHTML=`<span class="gh-spinner"></span>`;const{when:when="now",date:date="",time:time="",send_in_local_time:send_in_local_time=false,campaigns:campaigns=[],segment_type:segment_type="fixed",batching:batching=false,batch_interval,batch_interval_length,batch_amount}=getState();post(routes.v4.broadcasts,{object_id:getObject().ID,object_type:"email",query:getQuery(),date:date,time:time,send_now:when==="now",send_in_local_time:send_in_local_time,campaigns:campaigns.map(({ID})=>ID),segment_type:segment_type,batching:batching,batch_interval:batch_interval,batch_interval_length:batch_interval_length,batch_amount:batch_amount}).then(r=>{setState({step:"scheduled",broadcast:r.item})}).catch(err=>{dialog({message:err.message,type:"error"});console.log(err);switch(err.code){case"invalid_date":setState({step:"schedule"});break;default:setState({});break}})}},__("Confirm and schedule!","groundhogg"))])}},scheduled:{name:__("Scheduled"),icon:Dashicon("megaphone"),requirements:()=>getState().broadcast,render:()=>{return Fragment([`<p>${__("🎉 Your broadcast is being scheduled in the background!","groundhogg")}</p>`,Button({id:"re-schedule",className:"gh-button primary",style:{alignSelf:"flex-start"},onClick:e=>{setState({...initialState})}},sprintf("← %s",__("Schedule another broadcast","groundhogg")))])}}};const getSteps=()=>{const merged={};const overrides=getState().steps;for(let step in Steps){if(overrides.hasOwnProperty(step)){merged[step]={...Steps[step],...overrides[step]}}else{merged[step]=Steps[step]}}return merged};const BroadcastScheduler=()=>{const order=["object","campaigns","contacts","schedule","review","scheduled"];return Div({id:"broadcast-scheduler",className:"display-flex column gap-10",style:{width:"500px",maxWidth:"100%"}},[getState().step!=="scheduled"?Div({className:"gh-step-nav",style:{marginBottom:"20px"}},[...order.map(step=>Button({id:`select-${step}`,className:`gh-button icon ${getState().step===step?"primary":"secondary"}`,disabled:!getSteps()[step].requirements(),onClick:e=>{setState({step:step})}},getSteps()[step].icon)).reduce((steps,step,i)=>{if(i>0){steps.push(makeEl("hr",{className:"gh-step-nav-join"}))}steps.push(step);return steps},[])]):null,getSteps()[getState().step].render({getState:getState,getObject:getObject,setState:setState,getQuery:getQuery})])};Groundhogg.BroadcastScheduler=(newState={})=>{SearchesStore.maybeFetchItems();State.reset();setState({...newState},false);if(getState().searchMethod!=="filters"){updateTotalContacts(false)}return BroadcastScheduler()};Groundhogg.SendBroadcast=(selector,{email:email=false,...rest}={},{onScheduled:onScheduled=()=>{}})=>{document.querySelector(selector).append(Groundhogg.BroadcastScheduler({object:email,onScheduled:onScheduled}))};$(()=>{$("#gh-schedule-broadcast").on("click",e=>{e.preventDefault();Modal({},()=>Groundhogg.BroadcastScheduler())});if(typeof GroundhoggNewBroadcast!=="undefined"){document.getElementById("gh-broadcast-form-inline").append(Groundhogg.BroadcastScheduler({object:GroundhoggNewBroadcast.email,onScheduled:()=>{window.location.href=adminPageURL("gh_broadcasts",{status:"scheduled"})}}))}})})(jQuery);1 (function($){const{adminPageURL,bold,icons,dialog}=Groundhogg.element;const{emails:EmailsStore,searches:SearchesStore,contacts:ContactsStore,campaigns:CampaignsStore}=Groundhogg.stores;const{routes,post,ajax}=Groundhogg.api;const{createFilters}=Groundhogg.filters.functions;const{formatNumber,formatDateTime}=Groundhogg.formatting;const{debounce}=Groundhogg.functions;const{sprintf,__,_x,_n}=wp.i18n;const{isInTheFuture,getDate,date}=wp.date;const{Div,Button,Pg,Modal,Textarea,Select,ItemPicker,Fragment,Input,InputGroup,Iframe,makeEl,ButtonToggle,Label,Span,Toggle,Dashicon}=MakeEl;const initialState={step:"object",steps:[],object:null,when:"later",campaigns:[],searchMethod:"filters",searchMethods:[],totalContacts:0,date:date("Y-m-d"),time:date("H:00:00",getDate().setHours(getDate().getHours()+1)),broadcast:null,segment_type:"fixed",batching:false,batch_interval:"minutes",batch_interval_length:30,batch_amount:100,send_in_local_time:false};const getSearchMethods=()=>{return[...getState().searchMethods??[],{id:"filters",text:__("Search for contacts using filters.","groundhogg"),query:()=>({filters:getState().include_filters,exclude_filters:getState().exclude_filters})},{id:"all-contacts",text:__("All contacts.","groundhogg"),query:()=>({})},{id:"all-my-contacts",text:__("All contacts assigned to me.","groundhogg"),query:()=>({owner_id:Groundhogg.currentUser.ID})},{id:"confirmed-contacts",text:__("All confirmed contacts.","groundhogg"),query:()=>({optin_status:2})},...SearchesStore.getItems().map(({id,name})=>({id:id,text:sprintf(__("Saved search %s","groundhogg"),bold(name)),query:()=>({saved_search:id})}))]};const State=Groundhogg.createState({...initialState});const getQuery=()=>{let query={};const{searchMethod:searchMethod="filters"}=getState();query=getSearchMethods().find(({id})=>id===searchMethod).query();if(getObject()&&getObject().data.message_type!=="transactional"){query.marketable="yes"}return query};const updateTotalContacts=(morph=true)=>{return ContactsStore.count(getQuery()).then(total=>{setState({totalContacts:total},morph)})};const updateDurationEstimate=debounce(()=>ajax({action:"gh_estimate_send_duration",total_contacts:getState().totalContacts,batch_interval:getState().batch_interval,batch_interval_length:getState().batch_interval_length,batch_amount:getState().batch_amount}).then(r=>{const{time}=r.data;setState({duration_estimate:time})}),500);const getState=()=>State;const setState=(newState,morph=true)=>{State.set({...newState});if(morph){try{morphdom(document.getElementById("broadcast-scheduler"),BroadcastScheduler())}catch(e){console.log(e)}}};const getObject=()=>getState().object;const FromPreview=()=>Div({className:"from-preview display-flex gap-20 has-box-shadow"},[makeEl("img",{src:getObject().context.from_avatar,className:"from-avatar",height:40,width:40,style:{borderRadius:"50%"}}),Div({className:"subject-and-from"},[`<h2>${getObject().data.subject}</h2>`,`<span class="from-name">${getObject().context.from_name}</span> <span class="from-email"><${getObject().context.from_email}></span>`])]);const EmailPreview=()=>{return Div({className:"email-preview display-flex column",style:{overflow:"hidden",border:"1px solid #ccc",borderRadius:"5px"}},[FromPreview(),Iframe({id:`broadcast-email-preview`,height:400,style:{width:"100%"}},getObject().context.built)])};const Steps={object:{name:__("Email","groundhogg"),icon:icons.email,requirements:()=>true,render:()=>{return Fragment([Div({className:"display-flex column gap-10"},[`<p>${__("Select an email to send...","groundhogg")}</p>`,Div({className:"display-flex gap-10"},[ItemPicker({id:`broadcast-select-email`,noneSelected:__("Select an email to send...","groundhogg"),selected:getObject()?{id:getObject().ID,text:getObject().data.title}:[],multiple:false,style:{flexGrow:1},fetchOptions:search=>{return EmailsStore.fetchItems({search:search,status:"ready"}).then(emails=>emails.map(({ID,data})=>({id:ID,text:data.title})))},onChange:item=>{if(!item){setState({object:null});return}let email=EmailsStore.get(item.id);setState({object:email,campaigns:email.campaigns})}}),getObject()?Button({id:"go-to-campaigns",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"campaigns"})}},sprintf("%s →",__("Campaigns","groundhogg"))):null])]),getObject()?EmailPreview():null])}},campaigns:{name:__("Campaigns","groundhogg"),requirements:()=>getObject(),icon:Dashicon("flag"),render:()=>{return Fragment([`<p>${__("Use campaigns to organize your broadcasts! Select one or more campaigns...","groundhogg")}</p>`,ItemPicker({id:"broadcast-campaigns",noneSelected:__("Select a campaign...","groundhogg"),tags:true,selected:getState().campaigns.map(({ID,data})=>({id:ID,text:data.name})),fetchOptions:async search=>{let campaigns=await CampaignsStore.fetchItems({search:search,limit:20});return campaigns.map(({ID,data})=>({id:ID,text:data.name}))},createOption:async id=>{let campaign=await CampaignsStore.create({data:{name:id}});return{id:campaign.ID,text:campaign.data.name}},onChange:items=>setState({campaigns:items.map(({id})=>CampaignsStore.get(id))})}),Button({id:"go-to-schedule",className:"gh-button primary",style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"contacts"})}},sprintf("%s →",__("Contacts","groundhogg")))])}},contacts:{name:__("Contacts","groundhogg"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date),icon:icons.contact,render:()=>{return Fragment([`<p>${__("Select contacts to receive this broadcast...","groundhogg")}</p>`,ItemPicker({id:"select-search-method",multiple:false,selected:getSearchMethods().find(({id})=>id===getState().searchMethod),fetchOptions:async search=>{return getSearchMethods().filter(({text})=>text.match(new RegExp(search,"i")))},onChange:item=>{if(!item){setState({searchMethod:"filters"});updateTotalContacts();return}let{id}=item;setState({searchMethod:id});updateTotalContacts()}}),getState().searchMethod==="filters"?Div({id:"broadcast-include-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-include-filters",getState().include_filters,include_filters=>{setState({include_filters:include_filters},false);updateTotalContacts()}).init()})}}):null,getState().searchMethod==="filters"?Div({id:"broadcast-exclude-filters",onCreate:el=>{setTimeout(()=>{createFilters("#broadcast-exclude-filters",getState().exclude_filters,exclude_filters=>{setState({exclude_filters:exclude_filters},false);updateTotalContacts()}).init()})}}):null,`<div style="font-size: 14px">${sprintf(__("%s contacts will receive this broadcast.","groundhogg"),bold(formatNumber(getState().totalContacts)))}</div>`,State.when==="later"?Fragment([Div({className:"display-flex column gap-5"},[`<p>Which contacts should be included at the time of sending?</p>`,Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="fixed",onChange:e=>{if(e.target.checked){State.set({segment_type:"fixed"})}}}),"<b>Static Segment:</b> <i>Contacts</i> currently <i>within the segment</i>."]),Label({style:{fontsize:"14px"}},[Input({type:"radio",name:"segment_type",checked:State.segment_type==="dynamic",onChange:e=>{if(e.target.checked){State.set({segment_type:"dynamic"})}}}),"<b>Dynamic Segment:</b> <i>Contacts within the segment</i> at the time of sending."])])]):null,Button({id:"go-to-review",className:"gh-button primary",disabled:!getState().totalContacts,style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"schedule"})}},sprintf("%s →",__("Schedule","groundhogg")))])}},schedule:{name:__("Schedule","groundhogg"),icon:Dashicon("calendar"),requirements:()=>getObject(),render:()=>{return Fragment([Div({className:"space-between"},[`<p>${__("When do you want the broadcast to go out?","groundhogg")}</p>`,ButtonToggle({id:"send-when",options:[{id:"later",text:"Later"},{id:"now",text:"Now"}],selected:getState().when,onChange:when=>setState({when:when})})]),getState().when==="later"?Div({className:"gh-input-group"},[Input({type:"date",id:"send-date",name:"date",value:getState().date||"",min:date("Y-m-d"),onChange:e=>setState({date:e.target.value})}),Input({type:"time",id:"send-time",name:"time",value:getState().time||"",onChange:e=>setState({time:e.target.value})}),Button({className:"gh-button grey small",disabled:true},wp.date.getSettings().timezone.abbr||wp.date.getSettings().timezone.string||`UTC${wp.date.getSettings().timezone.offsetFormatted}`)]):null,getState().when==="later"?Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-local"><p>${__("Send in the contact's local time?","groundhogg")}</p></label>`,Toggle({id:"send-in-local",checked:getState().send_in_local_time,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>setState({send_in_local_time:e.target.checked})})]):null,"<div><hr></div>",Div({className:"display-flex gap-10 align-center"},[`<label for="send-in-batches"><p>${__("Send in batches?","groundhogg")}</p></label>`,Toggle({id:"send-in-batches",checked:getState().batching,onLabel:__("Yes"),offLabel:__("No"),onChange:e=>{setState({batching:e.target.checked});if(getState().batching){updateDurationEstimate()}}})]),getState().batching?Fragment([Pg({className:"display-flex gap-5 align-center"},["Send",Input({id:"batch-amount",name:"batch_amount",step:getState().batch_amount<=100?10:50,value:getState().batch_amount,onInput:e=>{setState({batch_amount:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"100px",paddingRight:0}}),"emails every",Input({id:"batch-interval-length",name:"batch_interval_length",step:getState().batch_interval==="minutes"?5:1,value:getState().batch_interval_length,onInput:e=>{setState({batch_interval_length:parseInt(e.target.value)},false);updateDurationEstimate()},type:"number",className:"number",style:{width:"60px",paddingRight:0}}),Select({id:"batch-interval",name:"batch_interval",selected:getState().batch_interval,onChange:e=>{setState({batch_interval:e.target.value},false);updateDurationEstimate()},options:{minutes:__("Minutes"),hours:__("Hours"),days:__("Days")}})]),getState().duration_estimate?Span({className:"pill yellow"},sprintf(__("It will take at least %s to send to %s contacts.","groundhogg"),bold(getState().duration_estimate),formatNumber(getState().totalContacts))):Span({},__("Estimating...","groundhogg"))]):null,"<div><hr></div>",Button({id:"go-to-contacts",className:"gh-button primary",disabled:getState().when==="later"&&!isInTheFuture(`${getState().date} ${getState().time}`),style:{alignSelf:"flex-end"},onClick:e=>{setState({step:"review"})}},sprintf("%s →",__("Review","groundhogg")))])}},review:{name:"Review",icon:Dashicon("thumbs-up"),requirements:()=>getObject()&&(getState().when==="now"||getState().time&&getState().date)&&getState().totalContacts,render:()=>{let preview;if(getState().when==="now"){preview=sprintf(__("Send %1$s to %2$s contacts <b>now</b>!","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)))}else{preview=sprintf(__("Send %1$s to %2$s contacts on %3$s.","groundhogg"),bold(getObject().data.title),bold(formatNumber(getState().totalContacts)),formatDateTime(`${getState().date} ${getState().time}`))}return Fragment([`<p>${preview}</p>`,getObject()?EmailPreview():null,Button({id:"confirm-and-schedule",className:"gh-button primary medium",onClick:e=>{e.target.innerHTML=`<span class="gh-spinner"></span>`;const{when:when="now",date:date="",time:time="",send_in_local_time:send_in_local_time=false,campaigns:campaigns=[],segment_type:segment_type="fixed",batching:batching=false,batch_interval,batch_interval_length,batch_amount}=getState();post(routes.v4.broadcasts,{object_id:getObject().ID,object_type:"email",query:getQuery(),date:date,time:time,send_now:when==="now",send_in_local_time:send_in_local_time,campaigns:campaigns.map(({ID})=>ID),segment_type:segment_type,batching:batching,batch_interval:batch_interval,batch_interval_length:batch_interval_length,batch_amount:batch_amount}).then(r=>{setState({step:"scheduled",broadcast:r.item})}).catch(err=>{dialog({message:err.message,type:"error"});console.log(err);switch(err.code){case"invalid_date":setState({step:"schedule"});break;default:setState({});break}})}},__("Confirm and schedule!","groundhogg"))])}},scheduled:{name:__("Scheduled"),icon:Dashicon("megaphone"),requirements:()=>getState().broadcast,render:()=>{return Fragment([`<p>${__("🎉 Your broadcast is being scheduled in the background!","groundhogg")}</p>`,Button({id:"re-schedule",className:"gh-button primary",style:{alignSelf:"flex-start"},onClick:e=>{setState({...initialState})}},sprintf("← %s",__("Schedule another broadcast","groundhogg")))])}}};const getSteps=()=>{const merged={};const overrides=getState().steps;for(let step in Steps){if(overrides.hasOwnProperty(step)){merged[step]={...Steps[step],...overrides[step]}}else{merged[step]=Steps[step]}}return merged};const BroadcastScheduler=()=>{const order=["object","campaigns","contacts","schedule","review","scheduled"];return Div({id:"broadcast-scheduler",className:"display-flex column gap-10",style:{width:"500px",maxWidth:"100%"}},[getState().step!=="scheduled"?Div({className:"gh-step-nav",style:{marginBottom:"20px"}},[...order.map(step=>Button({id:`select-${step}`,className:`gh-button icon ${getState().step===step?"primary":"secondary"}`,disabled:!getSteps()[step].requirements(),onClick:e=>{setState({step:step})}},getSteps()[step].icon)).reduce((steps,step,i)=>{if(i>0){steps.push(makeEl("hr",{className:"gh-step-nav-join"}))}steps.push(step);return steps},[])]):null,getSteps()[getState().step].render({getState:getState,getObject:getObject,setState:setState,getQuery:getQuery})])};Groundhogg.BroadcastScheduler=(newState={})=>{SearchesStore.maybeFetchItems();State.reset();setState({...newState},false);if(getState().searchMethod!=="filters"){updateTotalContacts(false)}return BroadcastScheduler()};Groundhogg.SendBroadcast=(selector,{email:email=false,...rest}={},{onScheduled:onScheduled=()=>{}})=>{document.querySelector(selector).append(Groundhogg.BroadcastScheduler({object:email,onScheduled:onScheduled}))};$(()=>{$("#gh-schedule-broadcast").on("click",e=>{e.preventDefault();Modal({},()=>Groundhogg.BroadcastScheduler())});if(typeof GroundhoggNewBroadcast!=="undefined"){document.getElementById("gh-broadcast-form-inline").append(Groundhogg.BroadcastScheduler({object:GroundhoggNewBroadcast.email,onScheduled:()=>{window.location.href=adminPageURL("gh_broadcasts",{status:"scheduled"})}}))}})})(jQuery); -
groundhogg/trunk/groundhogg.php
r3289364 r3293513 4 4 * Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash 5 5 * Description: CRM and marketing automation for WordPress 6 * Version: 4.1.2 6 * Version: 4.1.2.1 7 7 * Author: Groundhogg Inc. 8 8 * Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash … … 25 25 } 26 26 27 define( 'GROUNDHOGG_VERSION', '4.1.2 ' );28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.1. 1.2' );27 define( 'GROUNDHOGG_VERSION', '4.1.2.1' ); 28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.1.2' ); 29 29 30 30 define( 'GROUNDHOGG__FILE__', __FILE__ ); -
groundhogg/trunk/includes/classes/step.php
r3289364 r3293513 286 286 287 287 return array_filter( $steps, function ( Step $step ) { 288 return str_starts_with( $step->branch, "$this->ID" ); 288 289 if ( $this->is_benchmark() ){ 290 return $step->branch === "$this->ID"; 291 } 292 293 return str_starts_with( $step->branch, "$this->ID-" ); 289 294 } ); 290 295
Note: See TracChangeset
for help on using the changeset viewer.