ghost 5.3.0 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/content/themes/casper/assets/built/screen.css +1 -1
  2. package/content/themes/casper/assets/built/screen.css.map +1 -1
  3. package/content/themes/casper/assets/css/screen.css +22 -7
  4. package/content/themes/casper/default.hbs +8 -5
  5. package/content/themes/casper/package.json +1 -1
  6. package/core/built/assets/ghost-dark-739c1f5546bd048eeeb253965ef36712.css +1 -0
  7. package/core/built/assets/{ghost.min-f4bba3a2a5ef256b82641345505d4f0f.js → ghost.min-36b64813b14c45075770658269d4b478.js} +166 -155
  8. package/core/built/assets/ghost.min-5211776b9497f36fac8c9e5f2584cbcc.css +1 -0
  9. package/core/built/assets/{vendor.min-4076498ccd6c8412365f43b156084ed8.js → vendor.min-be0129c9c6897c9f10425e2402881d77.js} +14 -13
  10. package/core/frontend/helpers/total_members.js +17 -0
  11. package/core/frontend/helpers/total_paid_members.js +16 -0
  12. package/core/frontend/utils/member-count.js +50 -0
  13. package/core/frontend/web/middleware/cors.js +2 -1
  14. package/core/server/api/endpoints/offers-public.js +2 -2
  15. package/core/server/api/endpoints/offers.js +8 -8
  16. package/core/server/api/endpoints/settings.js +62 -30
  17. package/core/server/api/endpoints/utils/serializers/input/settings.js +1 -0
  18. package/core/server/api/endpoints/utils/serializers/output/index.js +0 -4
  19. package/core/server/api/endpoints/utils/serializers/output/mappers/index.js +1 -0
  20. package/core/server/api/endpoints/utils/serializers/output/mappers/offers.js +28 -0
  21. package/core/server/api/endpoints/utils/serializers/output/settings.js +2 -1
  22. package/core/server/api/endpoints/utils/validators/input/settings.js +22 -2
  23. package/core/server/models/tag.js +4 -0
  24. package/core/server/services/auth/session/express-session.js +1 -1
  25. package/core/server/services/members/SingleUseTokenProvider.js +3 -3
  26. package/core/server/services/members/config.js +4 -1
  27. package/core/server/services/members/middleware.js +14 -2
  28. package/core/server/services/members/settings.js +4 -90
  29. package/core/server/services/settings/emails/verify-email.js +166 -0
  30. package/core/server/services/settings/settings-bread-service.js +170 -4
  31. package/core/server/services/settings/settings-service.js +9 -1
  32. package/core/server/services/webhooks/serialize.js +5 -0
  33. package/core/server/web/admin/views/default-prod.html +4 -4
  34. package/core/server/web/admin/views/default.html +4 -4
  35. package/core/server/web/api/endpoints/admin/routes.js +6 -0
  36. package/core/server/web/api/endpoints/content/routes.js +2 -1
  37. package/core/server/web/api/middleware/cors.js +2 -1
  38. package/core/server/web/members/app.js +2 -4
  39. package/core/shared/config/defaults.json +3 -0
  40. package/package.json +12 -11
  41. package/yarn.lock +81 -68
  42. package/core/built/assets/ghost-dark-9e5d1f0dfae41232e5e34e4d0df53ae0.css +0 -1
  43. package/core/built/assets/ghost.min-e7cfbd1800f8e99b9158f74f1e39cd76.css +0 -1
  44. package/core/server/api/endpoints/utils/serializers/output/offers.js +0 -16
@@ -12611,20 +12611,21 @@ Object.keys(t).forEach((n=>{e.registerKeyCommand({str:n,run:()=>t[n](e,n)})})),t
12611
12611
  let e=this._calloutTextEditor.post.tailPosition().toRange()
12612
12612
  this._calloutTextEditor.selectRange(e)}_updatePayloadAttr(e,t){let n=this.args.payload
12613
12613
  Ember.set(n,e,t),this.args.saveCard?.(n,!1)}},w=C(_.prototype,"config",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=C(_.prototype,"feature",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),k=C(_.prototype,"store",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=C(_.prototype,"membersUtils",[l],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=C(_.prototype,"ui",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),j=C(_.prototype,"isPickerVisible",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),C(_.prototype,"registerElement",[d],Object.getOwnPropertyDescriptor(_.prototype,"registerElement"),_.prototype),C(_.prototype,"setCalloutText",[h],Object.getOwnPropertyDescriptor(_.prototype,"setCalloutText"),_.prototype),C(_.prototype,"setCalloutEmoji",[p],Object.getOwnPropertyDescriptor(_.prototype,"setCalloutEmoji"),_.prototype),C(_.prototype,"setBackgroundColor",[f],Object.getOwnPropertyDescriptor(_.prototype,"setBackgroundColor"),_.prototype),C(_.prototype,"leaveEditMode",[m],Object.getOwnPropertyDescriptor(_.prototype,"leaveEditMode"),_.prototype),C(_.prototype,"focusElement",[g],Object.getOwnPropertyDescriptor(_.prototype,"focusElement"),_.prototype),C(_.prototype,"registerEditor",[b],Object.getOwnPropertyDescriptor(_.prototype,"registerEditor"),_.prototype),C(_.prototype,"changeEmoji",[v],Object.getOwnPropertyDescriptor(_.prototype,"changeEmoji"),_.prototype),C(_.prototype,"toggleEmoji",[y],Object.getOwnPropertyDescriptor(_.prototype,"toggleEmoji"),_.prototype),_)
12614
- e.default=P,Ember._setComponentTemplate(T,P)})),define("koenig-editor/components/koenig-card-code",["exports","@tryghost/helpers"],(function(e,t){"use strict"
12615
- var n,r,i,o,a,s,l,u,c,d,h,p
12616
- function f(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function m(e,t,n,r,i){var o={}
12614
+ e.default=P,Ember._setComponentTemplate(T,P)})),define("koenig-editor/components/koenig-card-code",["exports","@glimmer/component","@tryghost/helpers"],(function(e,t,n){"use strict"
12615
+ var r,i,o,a,s,l,u,c
12616
+ function d(e,t,n,r,i){var o={}
12617
12617
  return Object.keys(r).forEach((function(e){o[e]=r[e]})),o.enumerable=!!o.enumerable,o.configurable=!!o.configurable,("value"in o||o.initializer)&&(o.writable=!0),o=n.slice().reverse().reduce((function(n,r){return r(e,t,n)||n}),o),i&&void 0!==o.initializer&&(o.value=o.initializer?o.initializer.call(i):void 0,o.initializer=void 0),void 0===o.initializer&&(Object.defineProperty(e,t,o),o=null),o}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
12618
- const g=Ember.HTMLBars.template({id:"TWAJ6MUL",block:'{"symbols":["card"],"statements":[[8,"koenig-card",[],[["@class","@style","@headerOffset","@toolbar","@payload","@isSelected","@isEditing","@selectCard","@deselectCard","@editCard","@saveCard","@saveAsSnippet","@onEnterEdit","@onLeaveEdit","@addParagraphAfterCard","@moveCursorToPrevSection","@moveCursorToNextSection","@editor"],[[30,[36,1],["ba b--white relative kg-card-hover miw-100",[30,[36,2],[[32,0,["isEditing"]]," bw2 pt1 pb1 pl2 nl6 pr6 nr6"],null]],null],[32,0,["cardStyle"]],[32,0,["headerOffset"]],[32,0,["toolbar"]],[32,0,["payload"]],[32,0,["isSelected"]],[32,0,["isEditing"]],[30,[36,0],[[32,0],[32,0,["selectCard"]]],null],[30,[36,0],[[32,0],[32,0,["deselectCard"]]],null],[30,[36,0],[[32,0],[32,0,["editCard"]]],null],[30,[36,0],[[32,0],[32,0,["saveCard"]]],null],[32,0,["saveAsSnippet"]],[30,[36,0],[[32,0],"enterEditMode"],null],[30,[36,0],[[32,0],"leaveEditMode"],null],[32,0,["addParagraphAfterCard"]],[32,0,["moveCursorToPrevSection"]],[32,0,["moveCursorToNextSection"]],[32,0,["editor"]]]],[["default"],[{"statements":[[2,"\\n"],[6,[37,2],[[32,0,["isEditing"]]],null,[["default","else"],[{"statements":[[2," "],[8,"gh-cm-editor",[],[["@value","@class","@textareaClass","@autofocus","@lineWrapping","@update","@mode"],[[32,0,["payload","code"]],"koenig-card-code--editor koenig-card-html--editor","o-0",true,true,[30,[36,0],[[32,0],"updateCode"],null],[32,0,["cmMode"]]]],null],[2,"\\n "],[10,"input"],[15,2,[30,[36,3],[[32,0,["payload","language"]]],null]],[15,"onblur",[30,[36,0],[[32,0],[30,[36,4],[[32,0,["payload","language"]]],null]],[["value"],["target.value"]]]],[14,"placeholder","Language..."],[14,0,"absolute w-20 pa1 ba b--lightgrey br2 f8 tracked-2 fw4 z-999 outline-0 anim-normal"],[15,5,[32,0,["languageInputStyle"]]],[14,4,"text"],[12],[13],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[10,"div"],[14,0,"koenig-card-html-rendered"],[12],[2,"\\n "],[10,"pre"],[12],[10,"code"],[15,0,[31,["line-numbers ",[30,[36,2],[[32,0,["payload","language"]],[30,[36,1],["language-",[32,0,["payload","language"]]],null]],null]]]],[12],[1,[32,0,["escapedCode"]]],[13],[13],[2,"\\n "],[13],[2,"\\n"],[6,[37,2],[[32,0,["payload","language"]]],null,[["default"],[{"statements":[[2," "],[10,"div"],[14,0,"absolute top-2 right-2 flex justify-center items-center pa2"],[12],[2,"\\n "],[10,"span"],[14,0,"db nudge-top--2 fw5 f8 midlightgrey"],[12],[1,[32,0,["payload","language"]]],[13],[2,"\\n "],[13],[2,"\\n"]],"parameters":[]}]]],[2," "],[10,"div"],[14,0,"koenig-card-click-overlay"],[12],[13],[2,"\\n"]],"parameters":[]}]]],[2,"\\n"],[6,[37,2],[[30,[36,8],[[30,[36,7],[[32,0,["isEditing"]]],null],[30,[36,6],[[32,0,["isSelected"]],[30,[36,5],[[32,0,["payload","caption"]]],null]],null]],null]],null,[["default"],[{"statements":[[2," "],[8,[32,1,["CaptionInput"]],[],[["@class","@caption","@update","@placeholder"],["z-999",[32,0,["payload","caption"]],[30,[36,0],[[32,0],"updateCaption"],null],"Type caption for code block (optional)"]],null],[2,"\\n"]],"parameters":[]}]]]],"parameters":[1]}]]]],"hasEval":false,"upvars":["action","concat","if","readonly","mut","clean-basic-html","or","not","and"]}',moduleName:"koenig-editor/components/koenig-card-code.hbs"}),{Handlebars:b}=Ember,{countWords:v}=t.utils,y={html:"htmlmixed",xhtml:"htmlmixed",hbs:"handlebars",js:"javascript"}
12619
- let _=(n=Ember.computed("payload.code"),r=Ember.computed("payload.code"),i=Ember.computed("isEditing"),o=Ember.computed("payload.code"),a=Ember.computed("payload.language"),s=Ember.computed("isEditing"),l=Ember.computed("showLanguageInput"),u=Ember._action,c=Ember._action,d=Ember._action,h=Ember._action,p=class extends Ember.Component{constructor(){super(...arguments),f(this,"payload",null),f(this,"isSelected",!1),f(this,"isEditing",!1),f(this,"headerOffset",0),f(this,"showLanguageInput",!0)}editCard(){}saveCard(){}selectCard(){}deselectCard(){}deleteCard(){}registerComponent(){}moveCursorToNextSection(){}moveCursorToPrevSection(){}addParagraphAfterCard(){}get isEmpty(){return Ember.isBlank(this.payload.code)}get counts(){return{wordCount:v(this.payload.code)}}get toolbar(){return!this.isEditing&&{items:[{buttonClass:"fw4 flex items-center white",icon:"koenig/kg-edit",iconClass:"fill-white",title:"Edit",text:"",action:Ember.run.bind(this,this.editCard)}]}}get escapedCode(){let e=b.Utils.escapeExpression(this.payload.code)
12620
- return Ember.String.htmlSafe(e)}get cmMode(){let{language:e}=this.payload
12621
- return y[e]||e}get cardStyle(){let e=this.isEditing?"background-color: #f4f8fb; border-color: #f4f8fb":""
12618
+ const h=Ember.HTMLBars.template({id:"cPsBaPAv",block:'{"symbols":["card","@payload","@isEditing","@headerOffset","@isSelected","@selectCard","@deselectCard","@editCard","@saveCard","@saveAsSnippet","@addParagraphAfterCard","@moveCursorToPrevSection","@moveCursorToNextSection","@editor"],"statements":[[8,"koenig-card",[],[["@class","@style","@headerOffset","@toolbar","@payload","@isSelected","@isEditing","@selectCard","@deselectCard","@editCard","@saveCard","@saveAsSnippet","@onEnterEdit","@onLeaveEdit","@addParagraphAfterCard","@moveCursorToPrevSection","@moveCursorToNextSection","@editor"],[[30,[36,0],["ba b--white relative kg-card-hover miw-100",[30,[36,1],[[32,3]," bw2 pt1 pb1 pl2 nl6 pr6 nr6"],null]],null],[32,0,["cardStyle"]],[32,4],[32,0,["toolbar"]],[32,2],[32,5],[32,3],[32,6],[32,7],[32,8],[32,9],[32,10],[32,0,["enterEditMode"]],[32,0,["leaveEditMode"]],[32,11],[32,12],[32,13],[32,14]]],[["default"],[{"statements":[[2,"\\n"],[6,[37,1],[[32,3]],null,[["default","else"],[{"statements":[[2," "],[8,"gh-cm-editor",[],[["@value","@class","@textareaClass","@autofocus","@lineWrapping","@update","@mode"],[[32,2,["code"]],"koenig-card-code--editor koenig-card-html--editor","o-0",true,true,[32,0,["updateCode"]],[32,0,["cmMode"]]]],null],[2,"\\n "],[11,"input"],[24,"aria-label","Code card language"],[16,2,[32,2,["language"]]],[24,"placeholder","Language..."],[24,0,"absolute w-20 pa1 ba b--lightgrey br2 f8 tracked-2 fw4 z-999 outline-0 anim-normal"],[16,5,[32,0,["languageInputStyle"]]],[24,4,"text"],[4,[38,2],["blur",[32,0,["updateLanguage"]]],null],[12],[13],[2,"\\n"]],"parameters":[]},{"statements":[[2," "],[10,"div"],[14,0,"koenig-card-html-rendered"],[12],[2,"\\n "],[10,"pre"],[12],[10,"code"],[15,0,[31,["line-numbers ",[30,[36,1],[[32,2,["language"]],[30,[36,0],["language-",[32,2,["language"]]],null]],null]]]],[12],[1,[32,0,["escapedCode"]]],[13],[13],[2,"\\n "],[13],[2,"\\n"],[6,[37,1],[[32,2,["language"]]],null,[["default"],[{"statements":[[2," "],[10,"div"],[14,0,"absolute top-2 right-2 flex justify-center items-center pa2"],[12],[2,"\\n "],[10,"span"],[14,0,"db nudge-top--2 fw5 f8 midlightgrey"],[12],[1,[32,2,["language"]]],[13],[2,"\\n "],[13],[2,"\\n"]],"parameters":[]}]]],[2," "],[10,"div"],[14,0,"koenig-card-click-overlay"],[12],[13],[2,"\\n"]],"parameters":[]}]]],[2,"\\n"],[6,[37,1],[[30,[36,6],[[30,[36,5],[[32,3]],null],[30,[36,4],[[32,5],[30,[36,3],[[32,2,["caption"]]],null]],null]],null]],null,[["default"],[{"statements":[[2," "],[8,[32,1,["CaptionInput"]],[],[["@class","@caption","@update","@placeholder"],["z-999",[32,2,["caption"]],[32,0,["updateCaption"]],"Type caption for code block (optional)"]],null],[2,"\\n"]],"parameters":[]}]]]],"parameters":[1]}]]]],"hasEval":false,"upvars":["concat","if","on","clean-basic-html","or","not","and"]}',moduleName:"koenig-editor/components/koenig-card-code.hbs"}),{Handlebars:p}=Ember,{countWords:f}=n.utils,m={html:"htmlmixed",xhtml:"htmlmixed",hbs:"handlebars",js:"javascript"}
12619
+ let g=(r=Ember._tracked,i=Ember._action,o=Ember._action,a=Ember._action,s=Ember._action,l=Ember._action,u=class extends t.default{get isEmpty(){return Ember.isBlank(this.args.payload.code)}get counts(){return{wordCount:f(this.args.payload.code)}}get toolbar(){return!this.args.isEditing&&{items:[{buttonClass:"fw4 flex items-center white",icon:"koenig/kg-edit",iconClass:"fill-white",title:"Edit",text:"",action:this.args.editCard}]}}get escapedCode(){let e=p.Utils.escapeExpression(this.args.payload.code)
12620
+ return Ember.String.htmlSafe(e)}get cmMode(){let{language:e}=this.args.payload
12621
+ return m[e]||e}get cardStyle(){let e=this.args.isEditing?"background-color: #f4f8fb; border-color: #f4f8fb":""
12622
12622
  return Ember.String.htmlSafe(e)}get languageInputStyle(){let e=["top: 6px","right: 6px"]
12623
- return this.showLanguageInput||e.push("opacity: 0"),Ember.String.htmlSafe(e.join("; "))}init(){super.init(...arguments)
12624
- let e=this.payload||{}
12625
- e.code||Ember.set(e,"code",""),this.set("payload",e),this.registerComponent(this)}updateCode(e){this._hideLanguageInput(),this._updatePayloadAttr("code",e)}updateCaption(e){this._updatePayloadAttr("caption",e)}enterEditMode(){this._addMousemoveHandler()}leaveEditMode(){this._removeMousemoveHandler(),this.isEmpty&&Ember.run.scheduleOnce("afterRender",this,this.deleteCard)}_updatePayloadAttr(e,t){let n=this.payload,r=this.saveCard
12626
- Ember.set(n,e,t),r(n,!1)}_hideLanguageInput(){this.set("showLanguageInput",!1)}_showLanguageInput(){this.set("showLanguageInput",!0)}_addMousemoveHandler(){this._mousemoveHandler=Ember.run.bind(this,this._showLanguageInput),window.addEventListener("mousemove",this._mousemoveHandler)}_removeMousemoveHandler(){window.removeEventListener("mousemove",this._mousemoveHandler)}},m(p.prototype,"isEmpty",[n],Object.getOwnPropertyDescriptor(p.prototype,"isEmpty"),p.prototype),m(p.prototype,"counts",[r],Object.getOwnPropertyDescriptor(p.prototype,"counts"),p.prototype),m(p.prototype,"toolbar",[i],Object.getOwnPropertyDescriptor(p.prototype,"toolbar"),p.prototype),m(p.prototype,"escapedCode",[o],Object.getOwnPropertyDescriptor(p.prototype,"escapedCode"),p.prototype),m(p.prototype,"cmMode",[a],Object.getOwnPropertyDescriptor(p.prototype,"cmMode"),p.prototype),m(p.prototype,"cardStyle",[s],Object.getOwnPropertyDescriptor(p.prototype,"cardStyle"),p.prototype),m(p.prototype,"languageInputStyle",[l],Object.getOwnPropertyDescriptor(p.prototype,"languageInputStyle"),p.prototype),m(p.prototype,"updateCode",[u],Object.getOwnPropertyDescriptor(p.prototype,"updateCode"),p.prototype),m(p.prototype,"updateCaption",[c],Object.getOwnPropertyDescriptor(p.prototype,"updateCaption"),p.prototype),m(p.prototype,"enterEditMode",[d],Object.getOwnPropertyDescriptor(p.prototype,"enterEditMode"),p.prototype),m(p.prototype,"leaveEditMode",[h],Object.getOwnPropertyDescriptor(p.prototype,"leaveEditMode"),p.prototype),p)
12627
- e.default=_,Ember._setComponentTemplate(g,_)})),define("koenig-editor/components/koenig-card-email-cta",["exports","mobiledoc-kit/utils/browser","@glimmer/component","koenig-editor/components/koenig-text-replacement-html-input","ember-concurrency"],(function(e,t,n,r,i){"use strict"
12623
+ return this.showLanguageInput||e.push("opacity: 0"),Ember.String.htmlSafe(e.join("; "))}constructor(){var e,t,n,r
12624
+ super(...arguments),e=this,t="showLanguageInput",r=this,(n=c)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(r):void 0})
12625
+ let i=this.args.payload||{}
12626
+ i.code||(i.code=""),this.payload=i,this.args.registerComponent(this)}updateCode(e){this._hideLanguageInput(),this._updatePayloadAttr("code",e)}updateCaption(e){this._updatePayloadAttr("caption",e)}updateLanguage(e){this._updatePayloadAttr("language",e.target.value)}enterEditMode(){this._addMousemoveHandler()}leaveEditMode(){this._removeMousemoveHandler(),this.isEmpty&&Ember.run.scheduleOnce("afterRender",this,this.args.deleteCard)}_updatePayloadAttr(e,t){let n=this.args.payload,r=this.args.saveCard
12627
+ n[e]=t,r(n,!1)}_hideLanguageInput(){this.showLanguageInput=!1}_showLanguageInput(){this.showLanguageInput=!0}_addMousemoveHandler(){this._mousemoveHandler=Ember.run.bind(this,this._showLanguageInput),window.addEventListener("mousemove",this._mousemoveHandler)}_removeMousemoveHandler(){window.removeEventListener("mousemove",this._mousemoveHandler)}},c=d(u.prototype,"showLanguageInput",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!0}}),d(u.prototype,"updateCode",[i],Object.getOwnPropertyDescriptor(u.prototype,"updateCode"),u.prototype),d(u.prototype,"updateCaption",[o],Object.getOwnPropertyDescriptor(u.prototype,"updateCaption"),u.prototype),d(u.prototype,"updateLanguage",[a],Object.getOwnPropertyDescriptor(u.prototype,"updateLanguage"),u.prototype),d(u.prototype,"enterEditMode",[s],Object.getOwnPropertyDescriptor(u.prototype,"enterEditMode"),u.prototype),d(u.prototype,"leaveEditMode",[l],Object.getOwnPropertyDescriptor(u.prototype,"leaveEditMode"),u.prototype),u)
12628
+ e.default=g,Ember._setComponentTemplate(h,g)})),define("koenig-editor/components/koenig-card-email-cta",["exports","mobiledoc-kit/utils/browser","@glimmer/component","koenig-editor/components/koenig-text-replacement-html-input","ember-concurrency"],(function(e,t,n,r,i){"use strict"
12628
12629
  var o,a,s,l,u,c,d,h,p,f,m,g,b,v,y,_,w,M,k,E,A,j,O,x,C,T,S,P,L,D
12629
12630
  function z(e,t,n,r){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(r):void 0})}function N(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function R(e,t,n,r,i){var o={}
12630
12631
  return Object.keys(r).forEach((function(e){o[e]=r[e]})),o.enumerable=!!o.enumerable,o.configurable=!!o.configurable,("value"in o||o.initializer)&&(o.writable=!0),o=n.slice().reverse().reduce((function(n,r){return r(e,t,n)||n}),o),i&&void 0!==o.initializer&&(o.value=o.initializer?o.initializer.call(i):void 0,o.initializer=void 0),void 0===o.initializer&&(Object.defineProperty(e,t,o),o=null),o}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0
@@ -20865,4 +20866,4 @@ if(!i)return v(e)
20865
20866
  if(!b(i)||v(i).isOlderThan(4)){var o=v(e).getStackTop()
20866
20867
  y(i,new p(o.client,c.a.clone(o.scope)))}return v(i)}catch(t){return v(e)}}(e):v(e)}function b(e){return!!(e&&e.__SENTRY__&&e.__SENTRY__.hub)}function v(e){return e&&e.__SENTRY__&&e.__SENTRY__.hub||(e.__SENTRY__=e.__SENTRY__||{},e.__SENTRY__.hub=new p),e.__SENTRY__.hub}function y(e,t){return!!e&&(e.__SENTRY__=e.__SENTRY__||{},e.__SENTRY__.hub=t,!0)}}]])
20867
20868
 
20868
- //# sourceMappingURL=vendor.min-4076498ccd6c8412365f43b156084ed8.map
20869
+ //# sourceMappingURL=vendor.min-be0129c9c6897c9f10425e2402881d77.map
@@ -0,0 +1,17 @@
1
+ // # Total Members Helper
2
+ // Usage: `{{total_members}}`
3
+
4
+ const {SafeString} = require('../services/handlebars');
5
+ const {memberCountRounding, getMemberStats} = require('../utils/member-count');
6
+
7
+ module.exports = async function total_members () { //eslint-disable-line
8
+ if (this.total) {
9
+ return new SafeString(memberCountRounding(this.total));
10
+ } else {
11
+ let memberStats = await getMemberStats();
12
+ const {total} = memberStats;
13
+ return new SafeString(total > 0 ? memberCountRounding(total) : 0);
14
+ }
15
+ };
16
+
17
+ module.exports.async = true;
@@ -0,0 +1,16 @@
1
+ // {{total_paid_members}} helper
2
+
3
+ const {SafeString} = require('../services/handlebars');
4
+ const {memberCountRounding, getMemberStats} = require('../utils/member-count');
5
+
6
+ module.exports = async function total_paid_members () { //eslint-disable-line
7
+ if (this.paid) {
8
+ return new SafeString(memberCountRounding(this.paid));
9
+ } else {
10
+ let memberStats = await getMemberStats();
11
+ const {paid} = memberStats;
12
+ return new SafeString(paid > 0 ? memberCountRounding(paid) : 0);
13
+ }
14
+ };
15
+
16
+ module.exports.async = true;
@@ -0,0 +1,50 @@
1
+ const humanNumber = require('human-number');
2
+ const {api} = require('../services/proxy');
3
+
4
+ async function getMemberStats() {
5
+ let memberStats = this.data || await api.stats.memberCountHistory.query();
6
+ const {free, paid, comped} = memberStats.meta.totals;
7
+ let total = free + paid + comped;
8
+ return {free, paid, comped, total};
9
+ }
10
+
11
+ const numberWithCommas = (n) => {
12
+ return n.toLocaleString();
13
+ };
14
+
15
+ const rounding = (n, roundTo) => {
16
+ return Math.floor(n / roundTo) * roundTo;
17
+ };
18
+
19
+ // Rounding https://github.com/TryGhost/Team/issues/1667
20
+ const memberCountRounding = (memberCount) => {
21
+ if (memberCount <= 50) {
22
+ return memberCount;
23
+ }
24
+
25
+ if (memberCount > 50 && memberCount <= 100) {
26
+ return `${numberWithCommas(rounding(memberCount, 10))}+`;
27
+ }
28
+
29
+ if (memberCount > 100 && memberCount <= 1000) {
30
+ return `${numberWithCommas(rounding(memberCount, 50))}+`;
31
+ }
32
+
33
+ if (memberCount > 1000 && memberCount <= 10000) {
34
+ return `${numberWithCommas(rounding(memberCount, 100))}+`;
35
+ }
36
+
37
+ if (memberCount > 10000 && memberCount <= 100000) {
38
+ return `${numberWithCommas(rounding(memberCount, 1000))}+`;
39
+ }
40
+
41
+ if (memberCount > 100000 && memberCount <= 1000000) {
42
+ return `${humanNumber(rounding(memberCount, 10000)).toLowerCase()}+`;
43
+ }
44
+
45
+ if (memberCount > 1000000) {
46
+ return `${humanNumber(rounding(memberCount, 100000)).toLowerCase()}+`;
47
+ }
48
+ };
49
+
50
+ module.exports = {memberCountRounding, getMemberStats};
@@ -13,7 +13,8 @@ function corsOptionsDelegate(req, callback) {
13
13
  const origin = req.header('Origin');
14
14
  const corsOptions = {
15
15
  origin: false, // disallow cross-origin requests by default
16
- credentials: true // required to allow admin-client to login to private sites
16
+ credentials: true, // required to allow admin-client to login to private sites
17
+ maxAge: config.get('caching:cors:maxAge')
17
18
  };
18
19
 
19
20
  if (!origin || origin === 'null') {
@@ -20,8 +20,8 @@ module.exports = {
20
20
  });
21
21
  }
22
22
 
23
- frame.response = {
24
- offers: [offer]
23
+ return {
24
+ data: [offer]
25
25
  };
26
26
  }
27
27
  }
@@ -16,8 +16,8 @@ module.exports = {
16
16
  permissions: true,
17
17
  async query(frame) {
18
18
  const offers = await offersService.api.listOffers(frame.options);
19
- frame.response = {
20
- offers
19
+ return {
20
+ data: offers
21
21
  };
22
22
  }
23
23
  },
@@ -33,8 +33,8 @@ module.exports = {
33
33
  });
34
34
  }
35
35
 
36
- frame.response = {
37
- offers: [offer]
36
+ return {
37
+ data: [offer]
38
38
  };
39
39
  }
40
40
  },
@@ -57,8 +57,8 @@ module.exports = {
57
57
  });
58
58
  }
59
59
 
60
- frame.response = {
61
- offers: [offer]
60
+ return {
61
+ data: [offer]
62
62
  };
63
63
  }
64
64
  },
@@ -70,8 +70,8 @@ module.exports = {
70
70
  },
71
71
  async query(frame) {
72
72
  const offer = await offersService.api.createOffer(frame.data.offers[0]);
73
- frame.response = {
74
- offers: [offer]
73
+ return {
74
+ data: [offer]
75
75
  };
76
76
  }
77
77
  }
@@ -2,17 +2,15 @@ const Promise = require('bluebird');
2
2
  const _ = require('lodash');
3
3
  const models = require('../../models');
4
4
  const routeSettings = require('../../services/route-settings');
5
- const tpl = require('@tryghost/tpl');
6
5
  const {BadRequestError} = require('@tryghost/errors');
7
6
  const settingsService = require('../../services/settings/settings-service');
8
7
  const membersService = require('../../services/members');
9
8
  const stripeService = require('../../services/stripe');
10
-
9
+ const tpl = require('@tryghost/tpl');
11
10
  const settingsBREADService = settingsService.getSettingsBREADServiceInstance();
12
11
 
13
12
  const messages = {
14
13
  failedSendingEmail: 'Failed Sending Email'
15
-
16
14
  };
17
15
 
18
16
  async function getStripeConnectData(frame) {
@@ -59,6 +57,65 @@ module.exports = {
59
57
  }
60
58
  },
61
59
 
60
+ verifyKeyUpdate: {
61
+ headers: {
62
+ cacheInvalidate: true
63
+ },
64
+ permissions: {
65
+ method: 'edit'
66
+ },
67
+ data: [
68
+ 'token'
69
+ ],
70
+ async query(frame) {
71
+ await settingsBREADService.verifyKeyUpdate(frame.data.token);
72
+
73
+ // We need to return all settings here, because we have calculated settings that might change
74
+ const browse = await settingsBREADService.browse(frame.options.context);
75
+
76
+ return browse;
77
+ }
78
+ },
79
+
80
+ /**
81
+ * @deprecated
82
+ */
83
+ updateMembersEmail: {
84
+ statusCode: 204,
85
+ permissions: {
86
+ method: 'edit'
87
+ },
88
+ data: [
89
+ 'email',
90
+ 'type'
91
+ ],
92
+ async query(frame) {
93
+ const {email, type} = frame.data;
94
+
95
+ try {
96
+ // Mapped internally to the newer method of changing emails
97
+ const actionToKeyMapping = {
98
+ supportAddressUpdate: 'members_support_address'
99
+ };
100
+ const edit = {
101
+ key: actionToKeyMapping[type],
102
+ value: email
103
+ };
104
+
105
+ await settingsBREADService.edit([edit], frame.options, null);
106
+ } catch (err) {
107
+ throw new BadRequestError({
108
+ err,
109
+ message: tpl(messages.failedSendingEmail)
110
+ });
111
+ }
112
+ }
113
+ },
114
+
115
+ /**
116
+ * @todo can get removed, since this is moved to verifyKeyUpdate
117
+ * @deprecated: keep to not break existing email verification links, but remove after 1 - 2 releases
118
+ */
62
119
  validateMembersEmailUpdate: {
63
120
  options: [
64
121
  'token',
@@ -108,33 +165,6 @@ module.exports = {
108
165
  }
109
166
  },
110
167
 
111
- updateMembersEmail: {
112
- statusCode: 204,
113
- permissions: {
114
- method: 'edit'
115
- },
116
- data: [
117
- 'email',
118
- 'type'
119
- ],
120
- async query(frame) {
121
- const {email, type} = frame.data;
122
-
123
- try {
124
- // Send magic link to update fromAddress
125
- await membersService.settings.sendEmailAddressUpdateMagicLink({
126
- email,
127
- type
128
- });
129
- } catch (err) {
130
- throw new BadRequestError({
131
- err,
132
- message: tpl(messages.failedSendingEmail)
133
- });
134
- }
135
- }
136
- },
137
-
138
168
  disconnectStripeConnectIntegration: {
139
169
  statusCode: 204,
140
170
  permissions: {
@@ -197,6 +227,8 @@ module.exports = {
197
227
 
198
228
  // We need to return all settings here, because we have calculated settings that might change
199
229
  const browse = await settingsBREADService.browse(frame.options.context);
230
+ browse.meta = result.meta || {};
231
+
200
232
  return browse;
201
233
  }
202
234
  },
@@ -31,6 +31,7 @@ const EDITABLE_SETTINGS = [
31
31
  'default_content_visibility',
32
32
  'default_content_visibility_tiers',
33
33
  'members_signup_access',
34
+ 'members_support_address',
34
35
  'stripe_secret_key',
35
36
  'stripe_publishable_key',
36
37
  'stripe_connect_integration_token',
@@ -125,10 +125,6 @@ module.exports = {
125
125
  return require('./session');
126
126
  },
127
127
 
128
- get offers() {
129
- return require('./offers');
130
- },
131
-
132
128
  get members_stripe_connect() {
133
129
  return require('./members-stripe-connect');
134
130
  }
@@ -10,6 +10,7 @@ module.exports = {
10
10
  settings: require('./settings'),
11
11
  snippets: require('./snippets'),
12
12
  tags: require('./tags'),
13
+ offers: require('./offers'),
13
14
  newsletters: require('./newsletters'),
14
15
  users: require('./users')
15
16
  };
@@ -0,0 +1,28 @@
1
+ const utils = require('../../../index');
2
+
3
+ module.exports = (model, frame) => {
4
+ // Offer data is already returned as json via members service
5
+ const jsonModel = model;
6
+
7
+ if (utils.isContentAPI(frame)) {
8
+ const serialized = {
9
+ id: jsonModel.id,
10
+ name: jsonModel.name,
11
+ display_title: jsonModel.display_title,
12
+ display_description: jsonModel.display_description,
13
+ type: jsonModel.type,
14
+ cadence: jsonModel.cadence,
15
+ amount: jsonModel.amount,
16
+ duration: jsonModel.duration,
17
+ duration_in_months: jsonModel.duration_in_months,
18
+ currency_restriction: jsonModel.currency_restriction,
19
+ currency: jsonModel.currency,
20
+ status: jsonModel.status,
21
+ tier: jsonModel.tier
22
+ };
23
+
24
+ return serialized;
25
+ }
26
+
27
+ return jsonModel;
28
+ };
@@ -52,7 +52,7 @@ function serializeSettings(models, apiConfig, frame) {
52
52
 
53
53
  frame.response = {
54
54
  settings: mappers.settings(filteredSettings),
55
- meta: {}
55
+ meta: models.meta ?? {}
56
56
  };
57
57
 
58
58
  if (frame.options.group) {
@@ -89,6 +89,7 @@ module.exports = {
89
89
  browse: serializeSettings,
90
90
  read: serializeSettings,
91
91
  edit: serializeSettings,
92
+ verifyKeyUpdate: serializeSettings,
92
93
 
93
94
  download: serializeData,
94
95
  upload: serializeData,
@@ -2,11 +2,12 @@ const Promise = require('bluebird');
2
2
  const _ = require('lodash');
3
3
  const {ValidationError, BadRequestError} = require('@tryghost/errors');
4
4
  const validator = require('@tryghost/validator');
5
+ const tpl = require('@tryghost/tpl');
5
6
 
6
7
  const messages = {
7
8
  invalidEmailReceived: 'Please send a valid email',
8
- invalidEmailTypeReceived: 'Invalid email type received',
9
- problemFindingSetting: 'Problem finding setting: {key}'
9
+ invalidEmailValueReceived: 'Please enter a valid email address.',
10
+ invalidEmailTypeReceived: 'Invalid email type received'
10
11
  };
11
12
 
12
13
  module.exports = {
@@ -22,6 +23,10 @@ module.exports = {
22
23
  'secondary_navigation'
23
24
  ];
24
25
 
26
+ const emailTypeSettings = [
27
+ 'members_support_address'
28
+ ];
29
+
25
30
  if (arrayTypeSettings.includes(setting.key)) {
26
31
  const typeError = new ValidationError({
27
32
  message: `Value in ${setting.key} should be an array.`,
@@ -43,6 +48,18 @@ module.exports = {
43
48
  }
44
49
  }
45
50
  }
51
+
52
+ if (emailTypeSettings.includes(setting.key)) {
53
+ const email = setting.value;
54
+
55
+ if (typeof email !== 'string' || (!validator.isEmail(email) && email !== 'noreply')) {
56
+ const typeError = new ValidationError({
57
+ message: tpl(messages.invalidEmailValueReceived),
58
+ property: setting.key
59
+ });
60
+ errors.push(typeError);
61
+ }
62
+ }
46
63
  });
47
64
 
48
65
  // Prevent setting icon to the resized one when sending all settings received from browse again in the edit endpoint
@@ -56,6 +73,9 @@ module.exports = {
56
73
  }
57
74
  },
58
75
 
76
+ /**
77
+ * @deprecated
78
+ */
59
79
  updateMembersEmail(apiConfig, frame) {
60
80
  const {email, type} = frame.data;
61
81
 
@@ -154,6 +154,10 @@ Tag = ghostBookshelf.Model.extend({
154
154
  actor_id: actor.id,
155
155
  actor_type: actor.type
156
156
  };
157
+ },
158
+
159
+ defaultColumnsToFetch() {
160
+ return ['id'];
157
161
  }
158
162
  }, {
159
163
  orderDefaultOptions: function orderDefaultOptions() {
@@ -23,7 +23,7 @@ function getExpressSessionMiddleware() {
23
23
  maxAge: constants.SIX_MONTH_MS,
24
24
  httpOnly: true,
25
25
  path: urlUtils.getSubdir() + '/ghost',
26
- sameSite: 'none',
26
+ sameSite: urlUtils.isSSL(config.get('url')) ? 'none' : 'lax',
27
27
  secure: urlUtils.isSSL(config.get('url'))
28
28
  }
29
29
  });
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- const {UnauthorizedError} = require('@tryghost/errors');
2
+ const {ValidationError} = require('@tryghost/errors');
3
3
 
4
4
  class SingleUseTokenProvider {
5
5
  /**
@@ -41,7 +41,7 @@ class SingleUseTokenProvider {
41
41
  const model = await this.model.findOne({token});
42
42
 
43
43
  if (!model) {
44
- throw new UnauthorizedError({
44
+ throw new ValidationError({
45
45
  message: 'Invalid token provided'
46
46
  });
47
47
  }
@@ -51,7 +51,7 @@ class SingleUseTokenProvider {
51
51
  const tokenLifetimeMilliseconds = Date.now() - createdAtEpoch;
52
52
 
53
53
  if (tokenLifetimeMilliseconds > this.validity) {
54
- throw new UnauthorizedError({
54
+ throw new ValidationError({
55
55
  message: 'Token expired'
56
56
  });
57
57
  }
@@ -154,11 +154,14 @@ class MembersConfigProvider {
154
154
  };
155
155
  }
156
156
 
157
- getSigninURL(token, type) {
157
+ getSigninURL(token, type, referrer) {
158
158
  const siteUrl = this._urlUtils.urlFor({relativeUrl: '/members/'}, true);
159
159
  const signinURL = new URL(siteUrl);
160
160
  signinURL.searchParams.set('token', token);
161
161
  signinURL.searchParams.set('action', type);
162
+ if (referrer) {
163
+ signinURL.searchParams.set('r', referrer);
164
+ }
162
165
  return signinURL.toString();
163
166
  }
164
167
  }
@@ -143,8 +143,8 @@ const createSessionFromMagicLink = async function (req, res, next) {
143
143
  // req.query is a plain object, copy it to a URLSearchParams object so we can call toString()
144
144
  const searchParams = new URLSearchParams('');
145
145
  Object.keys(req.query).forEach((param) => {
146
- // don't copy the token param
147
- if (param !== 'token') {
146
+ // don't copy the "token" or "r" params
147
+ if (param !== 'token' && param !== 'r') {
148
148
  searchParams.set(param, req.query[param]);
149
149
  }
150
150
  });
@@ -182,6 +182,18 @@ const createSessionFromMagicLink = async function (req, res, next) {
182
182
  }
183
183
  }
184
184
 
185
+ if (action === 'signin') {
186
+ const referrer = req.query.r;
187
+ const siteUrl = urlUtils.getSiteUrl();
188
+
189
+ if (referrer && referrer.startsWith(siteUrl)) {
190
+ const redirectUrl = new URL(referrer);
191
+ redirectUrl.searchParams.set('success', true);
192
+ redirectUrl.searchParams.set('action', 'signin');
193
+ return res.redirect(redirectUrl.pathname + redirectUrl.search);
194
+ }
195
+ }
196
+
185
197
  // Do a standard 302 redirect to the homepage, with success=true
186
198
  searchParams.set('success', true);
187
199
  res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);