Merge branch 'main' into patch-3

This commit is contained in:
Fernando Ochoa Olivares 2024-08-06 11:09:51 -06:00 committed by GitHub
commit 61015ff99d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 1421 additions and 1508 deletions

2
.envrc
View File

@ -1,2 +0,0 @@
layout node
use flake .

3
.gitignore vendored
View File

@ -66,9 +66,6 @@ typings/
# dotenv environment variables file # dotenv environment variables file
.env .env
# direnv
.direnv
# IDE # IDE
.idea/* .idea/*
*.iml *.iml

View File

@ -4,7 +4,7 @@ import articleBodyStyles from './articleBodyStyles';
import getUsername from '../utils/get-username'; import getUsername from '../utils/get-username';
import {ActivityPubAPI} from '../api/activitypub'; import {ActivityPubAPI} from '../api/activitypub';
import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub'; import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
import {Avatar, Button, ButtonGroup, Heading, List, ListItem, Page, SelectOption, SettingValue, ViewContainer, ViewTab} from '@tryghost/admin-x-design-system'; import {Avatar, Button, ButtonGroup, Heading, Icon, List, ListItem, Page, SelectOption, SettingValue, ViewContainer, ViewTab} from '@tryghost/admin-x-design-system';
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site'; import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
import {useQuery} from '@tanstack/react-query'; import {useQuery} from '@tanstack/react-query';
import {useRouting} from '@tryghost/admin-x-framework/routing'; import {useRouting} from '@tryghost/admin-x-framework/routing';
@ -123,6 +123,7 @@ const ActivityPubComponent: React.FC = () => {
actor={activity.actor} actor={activity.actor}
layout={selectedOption.value} layout={selectedOption.value}
object={activity.object} object={activity.object}
type={activity.type}
/> />
</li> </li>
))} ))}
@ -167,6 +168,7 @@ const ActivityPubComponent: React.FC = () => {
actor={activity.actor} actor={activity.actor}
layout={selectedOption.value} layout={selectedOption.value}
object={activity.object} object={activity.object}
type={activity.object.type}
/> />
</li> </li>
))} ))}
@ -320,7 +322,7 @@ ${image &&
); );
}; };
const ObjectContentDisplay: React.FC<{actor: ActorProperties, object: ObjectProperties, layout: string }> = ({actor, object, layout}) => { const ObjectContentDisplay: React.FC<{actor: ActorProperties, object: ObjectProperties, layout: string, type: string }> = ({actor, object, layout, type}) => {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(object.content || '', 'text/html'); const doc = parser.parseFromString(object.content || '', 'text/html');
@ -392,27 +394,40 @@ const ObjectContentDisplay: React.FC<{actor: ActorProperties, object: ObjectProp
setTimeout(() => setIsClicked(false), 300); // Reset the animation class after 300ms setTimeout(() => setIsClicked(false), 300); // Reset the animation class after 300ms
}; };
let author = actor;
if (type === 'Announce' && object.type === 'Note') {
author = typeof object.attributedTo === 'object' ? object.attributedTo as ActorProperties : actor;
}
if (layout === 'feed') { if (layout === 'feed') {
return ( return (
<> <>
{object && ( {object && (
<div className='group/article relative flex cursor-pointer items-start gap-4 pt-4'> <div className='group/article relative cursor-pointer pt-4'>
<img className='z-10 w-9 rounded' src={actor.icon?.url}/> {(type === 'Announce' && object.type === 'Note') && <div className='z-10 mb-2 flex items-center gap-4 text-grey-700'>
<div className='border-1 z-10 -mt-1 flex flex-col items-start justify-between border-b border-b-grey-200 pb-4' data-test-activity> <div className='z-10 flex w-10 justify-end'><Icon colorClass='text-grey-700' name='reload' size={'sm'}></Icon></div>
<div className='relative z-10 flex w-full overflow-visible text-[1.5rem]'> <span className='z-10'>{actor.name} reposted</span>
<p className='mr-1 truncate whitespace-nowrap font-bold' data-test-activity-heading>{actor.name}</p> </div>}
<span className='truncate text-grey-700'>{getUsername(actor)}</span> <div className='flex items-start gap-4'>
<span className='whitespace-nowrap text-grey-700 before:mx-1 before:content-["·"]'>{timestamp}</span> <img className='z-10 w-10 rounded' src={author.icon?.url}/>
</div> <div className='border-1 z-10 -mt-1 flex flex-col items-start justify-between border-b border-b-grey-200 pb-4' data-test-activity>
<div className='relative z-10 w-full gap-4'> <div className='relative z-10 mb-2 flex w-full flex-col overflow-visible text-[1.5rem]'>
<div className='flex flex-col'> <span className='mr-1 truncate whitespace-nowrap font-bold' data-test-activity-heading>{author.name}</span>
{object.name && <Heading className='mb-1 leading-tight' level={4} data-test-activity-heading>{object.name}</Heading>} <div className='flex'>
<p className='text-pretty text-[1.5rem] text-grey-900'>{plainTextContent}</p> <span className='truncate text-grey-700'>{getUsername(author)}</span>
{/* <p className='text-pretty text-md text-grey-900'>{object.content}</p> */} <span className='whitespace-nowrap text-grey-700 before:mx-1 before:content-["·"]'>{timestamp}</span>
{renderAttachment()} </div>
<div className='mt-3 flex gap-2'> </div>
<Button className={`self-start text-grey-500 transition-all hover:text-grey-800 ${isClicked ? 'bump' : ''} ${isLiked ? 'ap-red-heart text-red *:!fill-red hover:text-red' : ''}`} hideLabel={true} icon='heart' id="like" size='md' unstyled={true} onClick={handleLikeClick}/> <div className='relative z-10 w-full gap-4'>
<span className={`text-grey-800 ${isLiked ? 'opacity-100' : 'opacity-0'}`}>1</span> <div className='flex flex-col'>
{object.name && <Heading className='mb-1 leading-tight' level={4} data-test-activity-heading>{object.name}</Heading>}
<div dangerouslySetInnerHTML={({__html: object.content})} className='ap-note-content text-pretty text-[1.5rem] text-grey-900'></div>
{/* <p className='text-pretty text-md text-grey-900'>{object.content}</p> */}
{renderAttachment()}
<div className='mt-3 flex gap-2'>
<Button className={`self-start text-grey-500 transition-all hover:text-grey-800 ${isClicked ? 'bump' : ''} ${isLiked ? 'ap-red-heart text-red *:!fill-red hover:text-red' : ''}`} hideLabel={true} icon='heart' id="like" size='md' unstyled={true} onClick={handleLikeClick}/>
<span className={`text-grey-800 ${isLiked ? 'opacity-100' : 'opacity-0'}`}>1</span>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,4 +22,17 @@ animation: bump 0.3s ease-in-out;
.ap-red-heart path { .ap-red-heart path {
fill: #F50B23; fill: #F50B23;
} }
.ap-note-content a {
color: rgb(236 72 153) !important;
}
.ap-note-content a:hover {
color: rgb(219 39 119) !important;
}
.ap-note-content p + p {
margin-top: 1.5rem !important;
}

View File

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="Button-Refresh-Arrows--Streamline-Ultimate.svg" height="24" width="24"><desc>Button Refresh Arrows Streamline Icon: https://streamlinehq.com</desc><path d="m5.25 14.248 0 4.5 -4.5 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m18.75 9.748 0 -4.5 4.5 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M19.032 5.245A9.752 9.752 0 0 1 8.246 21" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M4.967 18.751A9.753 9.753 0 0 1 15.754 3" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 819 B

View File

@ -13,7 +13,7 @@ export type ObjectProperties = {
name: string; name: string;
content: string; content: string;
url?: string | undefined; url?: string | undefined;
attributedTo?: string | object[] | undefined; attributedTo?: object | string | object[] | undefined;
image?: string; image?: string;
published?: string; published?: string;
preview?: {type: string, content: string}; preview?: {type: string, content: string};

View File

@ -44,16 +44,16 @@
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "1.7.19", "@headlessui/react": "1.7.19",
"@tiptap/core": "2.5.8", "@tiptap/core": "2.5.9",
"@tiptap/extension-blockquote": "2.5.8", "@tiptap/extension-blockquote": "2.5.9",
"@tiptap/extension-document": "2.5.8", "@tiptap/extension-document": "2.5.9",
"@tiptap/extension-hard-break": "2.5.8", "@tiptap/extension-hard-break": "2.5.9",
"@tiptap/extension-link": "2.5.8", "@tiptap/extension-link": "2.5.9",
"@tiptap/extension-paragraph": "2.5.8", "@tiptap/extension-paragraph": "2.5.9",
"@tiptap/extension-placeholder": "2.5.8", "@tiptap/extension-placeholder": "2.5.9",
"@tiptap/extension-text": "2.5.8", "@tiptap/extension-text": "2.5.9",
"@tiptap/pm": "2.5.8", "@tiptap/pm": "2.5.9",
"@tiptap/react": "2.5.8", "@tiptap/react": "2.5.9",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-string-replace": "1.1.1" "react-string-replace": "1.1.1"

View File

@ -1,43 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1708501555,
"narHash": "sha256-zJaF0RkdIPbh8LTmnpW/E7tZYpqIE+MePzlWwUNob4c=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b50a77c03d640716296021ad58950b1bb0345799",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,49 +0,0 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
systems.url = "github:nix-systems/default";
};
outputs = {
systems,
nixpkgs,
...
} @ inputs: let
yarn_overlay = final: prev: {
yarn = prev.yarn.overrideAttrs(finalAttrs: prevAttrs: {
# This is to make sure that yarn runs the correct node version
# https://github.com/NixOS/nixpkgs/issues/145634#issuecomment-1627476963
installPhase = prevAttrs.installPhase + ''
ln -fs $out/libexec/yarn/bin/yarn $out/bin/yarn
ln -fs $out/libexec/yarn/bin/yarn.js $out/bin/yarn.js
ln -fs $out/libexec/yarn/bin/yarn $out/bin/yarnpkg
'';
});
};
# This gives us a central place to set the node version
node_overlay = final: prev: {
nodejs = prev.nodejs-18_x;
};
eachSystem = f:
nixpkgs.lib.genAttrs (import systems) (
system:
f ((nixpkgs.legacyPackages.${system}.extend yarn_overlay).extend node_overlay)
);
in {
devShells = eachSystem (pkgs: {
default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs
yarn
];
shellHook = ''
echo "node `${pkgs.nodejs}/bin/node --version`"
'';
};
});
};
}

View File

@ -24,6 +24,6 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2" "@tryghost/errors": "1.3.5"
} }
} }

View File

@ -95,7 +95,9 @@
@placeholder={{@bodyPlaceholder}} @placeholder={{@bodyPlaceholder}}
@cardConfig={{@cardOptions}} @cardConfig={{@cardOptions}}
@onChange={{@onBodyChange}} @onChange={{@onBodyChange}}
@updateSecondaryInstanceModel={{@updateSecondaryInstanceModel}}
@registerAPI={{this.registerEditorAPI}} @registerAPI={{this.registerEditorAPI}}
@registerSecondaryAPI={{this.registerSecondaryEditorAPI}}
@cursorDidExitAtTop={{if this.feature.editorExcerpt this.focusExcerpt this.focusTitle}} @cursorDidExitAtTop={{if this.feature.editorExcerpt this.focusExcerpt this.focusTitle}}
@updateWordCount={{@updateWordCount}} @updateWordCount={{@updateWordCount}}
@updatePostTkCount={{@updatePostTkCount}} @updatePostTkCount={{@updatePostTkCount}}

View File

@ -15,6 +15,7 @@ export default class GhKoenigEditorLexical extends Component {
uploadUrl = `${ghostPaths().apiRoot}/images/upload/`; uploadUrl = `${ghostPaths().apiRoot}/images/upload/`;
editorAPI = null; editorAPI = null;
secondaryEditorAPI = null;
skipFocusEditor = false; skipFocusEditor = false;
@tracked titleIsHovered = false; @tracked titleIsHovered = false;
@ -232,6 +233,12 @@ export default class GhKoenigEditorLexical extends Component {
this.args.registerAPI(API); this.args.registerAPI(API);
} }
@action
registerSecondaryEditorAPI(API) {
this.secondaryEditorAPI = API;
this.args.registerSecondaryAPI(API);
}
// focus the editor when the editor canvas is clicked below the editor content, // focus the editor when the editor canvas is clicked below the editor content,
// otherwise the browser will defocus the editor and the cursor will disappear // otherwise the browser will defocus the editor and the cursor will disappear
@action @action

View File

@ -853,6 +853,7 @@
post=this.post post=this.post
editorAPI=this.editorAPI editorAPI=this.editorAPI
toggleSettingsMenu=this.toggleSettingsMenu toggleSettingsMenu=this.toggleSettingsMenu
secondaryEditorAPI=this.secondaryEditorAPI
}} }}
@close={{this.closePostHistory}} @close={{this.closePostHistory}}
@modifier="total-overlay post-history" /> @modifier="total-overlay post-history" />

View File

@ -669,34 +669,43 @@ export default class KoenigLexicalEditor extends Component {
const multiplayerDocId = cardConfig.post.id; const multiplayerDocId = cardConfig.post.id;
const multiplayerUsername = this.session.user.name; const multiplayerUsername = this.session.user.name;
const KGEditorComponent = ({isInitInstance}) => {
return (
<div style={isInitInstance ? {visibility: 'hidden', position: 'absolute'} : {}}>
<KoenigComposer
editorResource={this.editorResource}
cardConfig={cardConfig}
enableMultiplayer={enableMultiplayer}
fileUploader={{useFileUpload, fileTypes}}
initialEditorState={this.args.lexical}
multiplayerUsername={multiplayerUsername}
multiplayerDocId={multiplayerDocId}
multiplayerEndpoint={multiplayerEndpoint}
onError={this.onError}
darkMode={this.feature.nightShift}
isTKEnabled={true}
>
<KoenigEditor
editorResource={this.editorResource}
cursorDidExitAtTop={isInitInstance ? null : this.args.cursorDidExitAtTop}
placeholderText={isInitInstance ? null : this.args.placeholderText}
darkMode={isInitInstance ? null : this.feature.nightShift}
onChange={isInitInstance ? this.args.updateSecondaryInstanceModel : this.args.onChange}
registerAPI={isInitInstance ? this.args.registerSecondaryAPI : this.args.registerAPI}
/>
<WordCountPlugin editorResource={this.editorResource} onChange={isInitInstance ? () => {} : this.args.updateWordCount} />
<TKCountPlugin editorResource={this.editorResource} onChange={isInitInstance ? () => {} : this.args.updatePostTkCount} />
</KoenigComposer>
</div>
);
};
return ( return (
<div className={['koenig-react-editor', 'koenig-lexical', this.args.className].filter(Boolean).join(' ')}> <div className={['koenig-react-editor', 'koenig-lexical', this.args.className].filter(Boolean).join(' ')}>
<ErrorHandler config={this.config}> <ErrorHandler config={this.config}>
<Suspense fallback={<p className="koenig-react-editor-loading">Loading editor...</p>}> <Suspense fallback={<p className="koenig-react-editor-loading">Loading editor...</p>}>
<KoenigComposer <KGEditorComponent />
editorResource={this.editorResource} <KGEditorComponent isInitInstance={true} />
cardConfig={cardConfig}
enableMultiplayer={enableMultiplayer}
fileUploader={{useFileUpload, fileTypes}}
initialEditorState={this.args.lexical}
multiplayerUsername={multiplayerUsername}
multiplayerDocId={multiplayerDocId}
multiplayerEndpoint={multiplayerEndpoint}
onError={this.onError}
darkMode={this.feature.nightShift}
isTKEnabled={true}
>
<KoenigEditor
editorResource={this.editorResource}
cursorDidExitAtTop={this.args.cursorDidExitAtTop}
placeholderText={this.args.placeholder}
darkMode={this.feature.nightShift}
onChange={this.args.onChange}
registerAPI={this.args.registerAPI}
/>
<WordCountPlugin editorResource={this.editorResource} onChange={this.args.updateWordCount} />
<TKCountPlugin editorResource={this.editorResource} onChange={this.args.updatePostTkCount} />
</KoenigComposer>
</Suspense> </Suspense>
</ErrorHandler> </ErrorHandler>
</div> </div>

View File

@ -33,6 +33,7 @@
@lexical={{this.selectedRevision.lexical}} @lexical={{this.selectedRevision.lexical}}
@cardConfig={{this.cardConfig}} @cardConfig={{this.cardConfig}}
@registerAPI={{this.registerSelectedEditorApi}} @registerAPI={{this.registerSelectedEditorApi}}
@registerSecondaryAPI={{this.registerSecondarySelectedEditorApi}}
/> />
</div> </div>
</div> </div>

View File

@ -31,6 +31,7 @@ export default class ModalPostHistory extends Component {
super(...arguments); super(...arguments);
this.post = this.args.model.post; this.post = this.args.model.post;
this.editorAPI = this.args.model.editorAPI; this.editorAPI = this.args.model.editorAPI;
this.secondaryEditorAPI = this.args.model.secondaryEditorAPI;
this.toggleSettingsMenu = this.args.model.toggleSettingsMenu; this.toggleSettingsMenu = this.args.model.toggleSettingsMenu;
} }
@ -101,6 +102,11 @@ export default class ModalPostHistory extends Component {
this.selectedEditor = api; this.selectedEditor = api;
} }
@action
registerSecondarySelectedEditorApi(api) {
this.secondarySelectedEditor = api;
}
@action @action
registerComparisonEditorApi(api) { registerComparisonEditorApi(api) {
this.comparisonEditor = api; this.comparisonEditor = api;
@ -130,6 +136,7 @@ export default class ModalPostHistory extends Component {
updateEditor: () => { updateEditor: () => {
const state = this.editorAPI.editorInstance.parseEditorState(revision.lexical); const state = this.editorAPI.editorInstance.parseEditorState(revision.lexical);
this.editorAPI.editorInstance.setEditorState(state); this.editorAPI.editorInstance.setEditorState(state);
this.secondaryEditorAPI.editorInstance.setEditorState(state);
}, },
closePostHistoryModal: () => { closePostHistoryModal: () => {
this.closeModal(); this.closeModal();

View File

@ -297,6 +297,11 @@ export default class LexicalEditorController extends Controller {
this._timedSaveTask.perform(); this._timedSaveTask.perform();
} }
@action
updateSecondaryInstanceModel(lexical) {
this.set('post.secondaryLexicalState', JSON.stringify(lexical));
}
@action @action
updateTitleScratch(title) { updateTitleScratch(title) {
this.set('post.titleScratch', title); this.set('post.titleScratch', title);
@ -423,6 +428,11 @@ export default class LexicalEditorController extends Controller {
this.editorAPI = API; this.editorAPI = API;
} }
@action
registerSecondaryEditorAPI(API) {
this.secondaryEditorAPI = API;
}
@action @action
clearFeatureImage() { clearFeatureImage() {
this.post.set('featureImage', null); this.post.set('featureImage', null);
@ -1221,7 +1231,6 @@ export default class LexicalEditorController extends Controller {
_timedSaveTask; _timedSaveTask;
/* Private methods -------------------------------------------------------*/ /* Private methods -------------------------------------------------------*/
_hasDirtyAttributes() { _hasDirtyAttributes() {
let post = this.post; let post = this.post;
@ -1229,8 +1238,7 @@ export default class LexicalEditorController extends Controller {
return false; return false;
} }
// if the Adapter failed to save the post isError will be true // If the Adapter failed to save the post, isError will be true, and we should consider the post still dirty.
// and we should consider the post still dirty.
if (post.get('isError')) { if (post.get('isError')) {
this._leaveModalReason = {reason: 'isError', context: post.errors.messages}; this._leaveModalReason = {reason: 'isError', context: post.errors.messages};
return true; return true;
@ -1245,37 +1253,32 @@ export default class LexicalEditorController extends Controller {
return true; return true;
} }
// titleScratch isn't an attr so needs a manual dirty check // Title scratch comparison
if (post.titleScratch !== post.title) { if (post.titleScratch !== post.title) {
this._leaveModalReason = {reason: 'title is different', context: {current: post.title, scratch: post.titleScratch}}; this._leaveModalReason = {reason: 'title is different', context: {current: post.title, scratch: post.titleScratch}};
return true; return true;
} }
// scratch isn't an attr so needs a manual dirty check // Lexical and scratch comparison
let lexical = post.get('lexical'); let lexical = post.get('lexical');
let scratch = post.get('lexicalScratch'); let scratch = post.get('lexicalScratch');
// additional guard in case we are trying to compare null with undefined let secondaryLexical = post.get('secondaryLexicalState');
if (scratch || lexical) {
if (scratch !== lexical) {
// lexical can dynamically set direction on loading editor state (e.g. "rtl"/"ltr") per the DOM context
// and we need to ignore this as a change from the user; see https://github.com/facebook/lexical/issues/4998
const scratchChildNodes = scratch ? JSON.parse(scratch).root?.children : [];
const lexicalChildNodes = lexical ? JSON.parse(lexical).root?.children : [];
// // nullling is typically faster than delete let lexicalChildNodes = lexical ? JSON.parse(lexical).root?.children : [];
scratchChildNodes.forEach(child => child.direction = null); let scratchChildNodes = scratch ? JSON.parse(scratch).root?.children : [];
lexicalChildNodes.forEach(child => child.direction = null); let secondaryLexicalChildNodes = secondaryLexical ? JSON.parse(secondaryLexical).root?.children : [];
if (JSON.stringify(scratchChildNodes) === JSON.stringify(lexicalChildNodes)) { lexicalChildNodes.forEach(child => child.direction = null);
return false; scratchChildNodes.forEach(child => child.direction = null);
} secondaryLexicalChildNodes.forEach(child => child.direction = null);
this._leaveModalReason = {reason: 'lexical is different', context: {current: lexical, scratch}}; // Compare initLexical with scratch
return true; let isSecondaryDirty = secondaryLexical && scratch && JSON.stringify(secondaryLexicalChildNodes) !== JSON.stringify(scratchChildNodes);
}
}
// new+unsaved posts always return `hasDirtyAttributes: true` // Compare lexical with scratch
let isLexicalDirty = lexical && scratch && JSON.stringify(lexicalChildNodes) !== JSON.stringify(scratchChildNodes);
// New+unsaved posts always return `hasDirtyAttributes: true`
// so we need a manual check to see if any // so we need a manual check to see if any
if (post.get('isNew')) { if (post.get('isNew')) {
let changedAttributes = Object.keys(post.changedAttributes()); let changedAttributes = Object.keys(post.changedAttributes());
@ -1286,15 +1289,26 @@ export default class LexicalEditorController extends Controller {
return changedAttributes.length ? true : false; return changedAttributes.length ? true : false;
} }
// we've covered all the non-tracked cases we care about so fall // We've covered all the non-tracked cases we care about so fall
// back on Ember Data's default dirty attribute checks // back on Ember Data's default dirty attribute checks
let {hasDirtyAttributes} = post; let {hasDirtyAttributes} = post;
if (hasDirtyAttributes) { if (hasDirtyAttributes) {
this._leaveModalReason = {reason: 'post.hasDirtyAttributes === true', context: post.changedAttributes()}; this._leaveModalReason = {reason: 'post.hasDirtyAttributes === true', context: post.changedAttributes()};
return true;
} }
return hasDirtyAttributes; // If either comparison is not dirty, return false, because scratch is always up to date.
if (!isSecondaryDirty || !isLexicalDirty) {
return false;
}
// If both comparisons are dirty, consider the post dirty
if (isSecondaryDirty && isLexicalDirty) {
this._leaveModalReason = {reason: 'initLexical and lexical are different from scratch', context: {secondaryLexical, lexical, scratch}};
return true;
}
return false;
} }
_showSaveNotification(prevStatus, status, delayed) { _showSaveNotification(prevStatus, status, delayed) {

View File

@ -136,6 +136,9 @@ export default Model.extend(Comparable, ValidationEngine, {
scratch: null, scratch: null,
lexicalScratch: null, lexicalScratch: null,
titleScratch: null, titleScratch: null,
//This is used to store the initial lexical state from the
// secondary editor to get the schema up to date in case its outdated
secondaryLexicalState: null,
// For use by date/time pickers - will be validated then converted to UTC // For use by date/time pickers - will be validated then converted to UTC
// on save. Updated by an observer whenever publishedAtUTC changes. // on save. Updated by an observer whenever publishedAtUTC changes.

View File

@ -73,6 +73,7 @@
@body={{readonly this.post.lexicalScratch}} @body={{readonly this.post.lexicalScratch}}
@bodyPlaceholder={{concat "Begin writing your " this.post.displayName "..."}} @bodyPlaceholder={{concat "Begin writing your " this.post.displayName "..."}}
@onBodyChange={{this.updateScratch}} @onBodyChange={{this.updateScratch}}
@updateSecondaryInstanceModel={{this.updateSecondaryInstanceModel}}
@headerOffset={{editor.headerHeight}} @headerOffset={{editor.headerHeight}}
@scrollContainerSelector=".gh-koenig-editor" @scrollContainerSelector=".gh-koenig-editor"
@scrollOffsetBottomSelector=".gh-mobile-nav-bar" @scrollOffsetBottomSelector=".gh-mobile-nav-bar"
@ -97,6 +98,7 @@
}} }}
@postType={{this.post.displayName}} @postType={{this.post.displayName}}
@registerAPI={{this.registerEditorAPI}} @registerAPI={{this.registerEditorAPI}}
@registerSecondaryAPI={{this.registerSecondaryEditorAPI}}
@savePostTask={{this.savePostTask}} @savePostTask={{this.savePostTask}}
/> />
@ -136,6 +138,7 @@
@updateSlugTask={{this.updateSlugTask}} @updateSlugTask={{this.updateSlugTask}}
@savePostTask={{this.savePostTask}} @savePostTask={{this.savePostTask}}
@editorAPI={{this.editorAPI}} @editorAPI={{this.editorAPI}}
@secondaryEditorAPI={{this.secondaryEditorAPI}}
@toggleSettingsMenu={{this.toggleSettingsMenu}} @toggleSettingsMenu={{this.toggleSettingsMenu}}
/> />
{{/if}} {{/if}}

View File

@ -208,7 +208,8 @@ describe('Unit: Controller: lexical-editor', function () {
titleScratch: 'this is a title', titleScratch: 'this is a title',
status: 'published', status: 'published',
lexical: initialLexicalString, lexical: initialLexicalString,
lexicalScratch: initialLexicalString lexicalScratch: initialLexicalString,
secondaryLexicalState: initialLexicalString
})); }));
// synthetically update the lexicalScratch as if the editor itself made the modifications on loading the initial editorState // synthetically update the lexicalScratch as if the editor itself made the modifications on loading the initial editorState
@ -225,5 +226,47 @@ describe('Unit: Controller: lexical-editor', function () {
isDirty = controller.get('hasDirtyAttributes'); isDirty = controller.get('hasDirtyAttributes');
expect(isDirty).to.be.true; expect(isDirty).to.be.true;
}); });
it('dirty is false if secondaryLexical and scratch matches, but lexical is outdated', async function () {
const initialLexicalString = `{"root":{"children":[{"children": [{"detail": 0,"format": 0,"mode": "normal","style": "","text": "Sample content","type": "extended-text","version": 1}],"direction": null,"format": "","indent": 0,"type": "paragraph","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "root","version": 1}}`;
const lexicalScratch = `{"root":{"children":[{"children": [{"detail": 0,"format": 0,"mode": "normal","style": "","text": "Sample content","type": "extended-text","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "paragraph","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "root","version": 1}}`;
const secondLexicalInstance = `{"root":{"children":[{"children": [{"detail": 0,"format": 0,"mode": "normal","style": "","text": "Here's some new text","type": "extended-text","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "paragraph","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "root","version": 1}}`;
let controller = this.owner.lookup('controller:lexical-editor');
controller.set('post', EmberObject.create({
title: 'this is a title',
titleScratch: 'this is a title',
status: 'published',
lexical: initialLexicalString,
lexicalScratch: lexicalScratch,
secondaryLexicalState: secondLexicalInstance
}));
let isDirty = controller.get('hasDirtyAttributes');
expect(isDirty).to.be.false;
});
it('dirty is true if secondaryLexical and lexical does not match scratch', async function () {
const initialLexicalString = `{"root":{"children":[{"children": [{"detail": 0,"format": 0,"mode": "normal","style": "","text": "Sample content","type": "extended-text","version": 1}],"direction": null,"format": "","indent": 0,"type": "paragraph","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "root","version": 1}}`;
const lexicalScratch = `{"root":{"children":[{"children": [{"detail": 0,"format": 0,"mode": "normal","style": "","text": "Sample content1234","type": "extended-text","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "paragraph","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "root","version": 1}}`;
const secondLexicalInstance = `{"root":{"children":[{"children": [{"detail": 0,"format": 0,"mode": "normal","style": "","text": "Here's some new text","type": "extended-text","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "paragraph","version": 1}],"direction": "ltr","format": "","indent": 0,"type": "root","version": 1}}`;
let controller = this.owner.lookup('controller:lexical-editor');
controller.set('post', EmberObject.create({
title: 'this is a title',
titleScratch: 'this is a title',
status: 'published',
lexical: initialLexicalString,
lexicalScratch: lexicalScratch,
secondaryLexicalState: secondLexicalInstance
}));
controller.send('updateScratch',JSON.parse(lexicalScratch));
let isDirty = controller.get('hasDirtyAttributes');
expect(isDirty).to.be.true;
});
}); });
}); });

View File

@ -24,11 +24,11 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/promise": "0.3.10", "@tryghost/promise": "0.3.12",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"@tryghost/validator": "0.2.11", "@tryghost/validator": "0.2.14",
"jsonpath": "1.1.1", "jsonpath": "1.1.1",
"lodash": "4.17.21" "lodash": "4.17.21"
} }

View File

@ -24,8 +24,8 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"bson-objectid": "2.0.4" "bson-objectid": "2.0.4"
} }
} }

View File

@ -24,6 +24,6 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/logging": "2.4.15" "@tryghost/logging": "2.4.18"
} }
} }

View File

@ -26,14 +26,14 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/in-memory-repository": "0.0.0", "@tryghost/in-memory-repository": "0.0.0",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/nql": "0.12.3", "@tryghost/nql": "0.12.3",
"@tryghost/nql-filter-expansions": "0.0.0", "@tryghost/nql-filter-expansions": "0.0.0",
"@tryghost/post-events": "0.0.0", "@tryghost/post-events": "0.0.0",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",
"lodash": "4.17.21" "lodash": "4.17.21"
}, },

View File

@ -17,6 +17,7 @@ class StaffServiceWrapper {
const mailer = new GhostMailer(); const mailer = new GhostMailer();
const settingsCache = require('../../../shared/settings-cache'); const settingsCache = require('../../../shared/settings-cache');
const urlUtils = require('../../../shared/url-utils'); const urlUtils = require('../../../shared/url-utils');
const {blogIcon} = require('../../../server/lib/image');
const settingsHelpers = require('../settings-helpers'); const settingsHelpers = require('../settings-helpers');
this.api = new StaffService({ this.api = new StaffService({
@ -26,6 +27,7 @@ class StaffServiceWrapper {
settingsHelpers, settingsHelpers,
settingsCache, settingsCache,
urlUtils, urlUtils,
blogIcon,
DomainEvents, DomainEvents,
memberAttributionService: memberAttribution.service, memberAttributionService: memberAttribution.service,
labs labs

View File

@ -76,7 +76,7 @@
"@tryghost/api-framework": "0.0.0", "@tryghost/api-framework": "0.0.0",
"@tryghost/api-version-compatibility-service": "0.0.0", "@tryghost/api-version-compatibility-service": "0.0.0",
"@tryghost/audience-feedback": "0.0.0", "@tryghost/audience-feedback": "0.0.0",
"@tryghost/bookshelf-plugins": "0.6.19", "@tryghost/bookshelf-plugins": "0.6.21",
"@tryghost/bootstrap-socket": "0.0.0", "@tryghost/bootstrap-socket": "0.0.0",
"@tryghost/collections": "0.0.0", "@tryghost/collections": "0.0.0",
"@tryghost/color-utils": "0.2.2", "@tryghost/color-utils": "0.2.2",
@ -84,24 +84,24 @@
"@tryghost/constants": "0.0.0", "@tryghost/constants": "0.0.0",
"@tryghost/custom-theme-settings-service": "0.0.0", "@tryghost/custom-theme-settings-service": "0.0.0",
"@tryghost/data-generator": "0.0.0", "@tryghost/data-generator": "0.0.0",
"@tryghost/database-info": "0.3.24", "@tryghost/database-info": "0.3.27",
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/domain-events": "0.0.0", "@tryghost/domain-events": "0.0.0",
"@tryghost/donations": "0.0.0", "@tryghost/donations": "0.0.0",
"@tryghost/dynamic-routing-events": "0.0.0", "@tryghost/dynamic-routing-events": "0.0.0",
"@tryghost/email-analytics-provider-mailgun": "0.0.0", "@tryghost/email-analytics-provider-mailgun": "0.0.0",
"@tryghost/email-analytics-service": "0.0.0", "@tryghost/email-analytics-service": "0.0.0",
"@tryghost/email-content-generator": "0.0.0", "@tryghost/email-content-generator": "0.0.0",
"@tryghost/email-mock-receiver": "0.3.6", "@tryghost/email-mock-receiver": "0.3.8",
"@tryghost/email-service": "0.0.0", "@tryghost/email-service": "0.0.0",
"@tryghost/email-suppression-list": "0.0.0", "@tryghost/email-suppression-list": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/express-dynamic-redirects": "0.0.0", "@tryghost/express-dynamic-redirects": "0.0.0",
"@tryghost/external-media-inliner": "0.0.0", "@tryghost/external-media-inliner": "0.0.0",
"@tryghost/ghost": "0.0.0", "@tryghost/ghost": "0.0.0",
"@tryghost/helpers": "1.1.90", "@tryghost/helpers": "1.1.90",
"@tryghost/html-to-plaintext": "0.0.0", "@tryghost/html-to-plaintext": "0.0.0",
"@tryghost/http-cache-utils": "0.1.15", "@tryghost/http-cache-utils": "0.1.17",
"@tryghost/i18n": "0.0.0", "@tryghost/i18n": "0.0.0",
"@tryghost/image-transform": "1.3.0", "@tryghost/image-transform": "1.3.0",
"@tryghost/importer-handler-content-files": "0.0.0", "@tryghost/importer-handler-content-files": "0.0.0",
@ -119,7 +119,7 @@
"@tryghost/link-redirects": "0.0.0", "@tryghost/link-redirects": "0.0.0",
"@tryghost/link-replacer": "0.0.0", "@tryghost/link-replacer": "0.0.0",
"@tryghost/link-tracking": "0.0.0", "@tryghost/link-tracking": "0.0.0",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/magic-link": "0.0.0", "@tryghost/magic-link": "0.0.0",
"@tryghost/mail-events": "0.0.0", "@tryghost/mail-events": "0.0.0",
"@tryghost/mailgun-client": "0.0.0", "@tryghost/mailgun-client": "0.0.0",
@ -143,16 +143,16 @@
"@tryghost/mw-session-from-token": "0.0.0", "@tryghost/mw-session-from-token": "0.0.0",
"@tryghost/mw-version-match": "0.0.0", "@tryghost/mw-version-match": "0.0.0",
"@tryghost/mw-vhost": "0.0.0", "@tryghost/mw-vhost": "0.0.0",
"@tryghost/nodemailer": "0.3.42", "@tryghost/nodemailer": "0.3.45",
"@tryghost/nql": "0.12.3", "@tryghost/nql": "0.12.3",
"@tryghost/oembed-service": "0.0.0", "@tryghost/oembed-service": "0.0.0",
"@tryghost/package-json": "0.0.0", "@tryghost/package-json": "0.0.0",
"@tryghost/post-revisions": "0.0.0", "@tryghost/post-revisions": "0.0.0",
"@tryghost/posts-service": "0.0.0", "@tryghost/posts-service": "0.0.0",
"@tryghost/pretty-cli": "1.2.42", "@tryghost/pretty-cli": "1.2.44",
"@tryghost/promise": "0.3.10", "@tryghost/promise": "0.3.12",
"@tryghost/recommendations": "0.0.0", "@tryghost/recommendations": "0.0.0",
"@tryghost/request": "1.0.5", "@tryghost/request": "1.0.8",
"@tryghost/security": "0.0.0", "@tryghost/security": "0.0.0",
"@tryghost/session-service": "0.0.0", "@tryghost/session-service": "0.0.0",
"@tryghost/settings-path-manager": "0.0.0", "@tryghost/settings-path-manager": "0.0.0",
@ -162,14 +162,14 @@
"@tryghost/stats-service": "0.0.0", "@tryghost/stats-service": "0.0.0",
"@tryghost/string": "0.2.12", "@tryghost/string": "0.2.12",
"@tryghost/tiers": "0.0.0", "@tryghost/tiers": "0.0.0",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"@tryghost/update-check-service": "0.0.0", "@tryghost/update-check-service": "0.0.0",
"@tryghost/url-utils": "4.4.8", "@tryghost/url-utils": "4.4.8",
"@tryghost/validator": "0.2.11", "@tryghost/validator": "0.2.14",
"@tryghost/verification-trigger": "0.0.0", "@tryghost/verification-trigger": "0.0.0",
"@tryghost/version": "0.1.28", "@tryghost/version": "0.1.30",
"@tryghost/webmentions": "0.0.0", "@tryghost/webmentions": "0.0.0",
"@tryghost/zip": "1.1.43", "@tryghost/zip": "1.1.46",
"amperize": "0.6.1", "amperize": "0.6.1",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"bookshelf": "1.2.0", "bookshelf": "1.2.0",
@ -210,7 +210,7 @@
"knex-migrator": "5.2.1", "knex-migrator": "5.2.1",
"lib0": "0.2.94", "lib0": "0.2.94",
"lodash": "4.17.21", "lodash": "4.17.21",
"luxon": "3.4.4", "luxon": "3.5.0",
"moment": "2.24.0", "moment": "2.24.0",
"moment-timezone": "0.5.45", "moment-timezone": "0.5.45",
"multer": "1.4.4", "multer": "1.4.4",
@ -237,8 +237,8 @@
"devDependencies": { "devDependencies": {
"@actions/core": "1.10.1", "@actions/core": "1.10.1",
"@playwright/test": "1.38.1", "@playwright/test": "1.38.1",
"@tryghost/express-test": "0.13.12", "@tryghost/express-test": "0.13.15",
"@tryghost/webhook-mock-receiver": "0.2.12", "@tryghost/webhook-mock-receiver": "0.2.14",
"@types/common-tags": "1.8.4", "@types/common-tags": "1.8.4",
"c8": "8.0.1", "c8": "8.0.1",
"cli-progress": "3.12.0", "cli-progress": "3.12.0",
@ -265,8 +265,8 @@
"toml": "3.0.0" "toml": "3.0.0"
}, },
"resolutions": { "resolutions": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"jackspeak": "2.1.1", "jackspeak": "2.1.1",
"moment": "2.24.0", "moment": "2.24.0",
"moment-timezone": "0.5.45" "moment-timezone": "0.5.45"

View File

@ -78,7 +78,7 @@ const createPage = async (page, {title = 'Hello world', body = 'This is my post
await page.locator('[data-test-editor-title-input]').fill(title); await page.locator('[data-test-editor-title-input]').fill(title);
// wait for editor to be ready // wait for editor to be ready
await expect(page.locator('[data-lexical-editor="true"]')).toBeVisible(); await expect(page.locator('[data-lexical-editor="true"]').first()).toBeVisible();
// Continue to the body by pressing enter // Continue to the body by pressing enter
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
@ -304,7 +304,7 @@ test.describe('Publishing', () => {
await expect(publishedHeader).toContainText(date.toFormat('LLL d, yyyy')); await expect(publishedHeader).toContainText(date.toFormat('LLL d, yyyy'));
// add some extra text to the post // add some extra text to the post
await adminPage.locator('[data-kg="editor"]').click(); await adminPage.locator('[data-kg="editor"]').first().click();
await adminPage.waitForTimeout(200); // await adminPage.waitForTimeout(200); //
await adminPage.keyboard.type(' This is some updated text.'); await adminPage.keyboard.type(' This is some updated text.');

View File

@ -423,7 +423,7 @@ const createPostDraft = async (page, {title = 'Hello world', body = 'This is my
await page.locator('[data-test-editor-title-input]').fill(title); await page.locator('[data-test-editor-title-input]').fill(title);
// wait for editor to be ready // wait for editor to be ready
await expect(page.locator('[data-lexical-editor="true"]')).toBeVisible(); await expect(page.locator('[data-lexical-editor="true"]').first()).toBeVisible();
// Continue to the body by pressing enter // Continue to the body by pressing enter
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');

View File

@ -25,10 +25,10 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/nql": "0.12.3", "@tryghost/nql": "0.12.3",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"lodash": "4.17.21" "lodash": "4.17.21"
} }
} }

View File

@ -18,7 +18,7 @@
"lib" "lib"
], ],
"devDependencies": { "devDependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"c8": "8.0.1", "c8": "8.0.1",
"knex": "2.4.2", "knex": "2.4.2",
"mocha": "10.2.0", "mocha": "10.2.0",
@ -27,7 +27,7 @@
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "7.6.0", "@faker-js/faker": "7.6.0",
"@tryghost/root-utils": "0.3.28", "@tryghost/root-utils": "0.3.30",
"@tryghost/string": "0.2.12", "@tryghost/string": "0.2.12",
"csv-writer": "1.6.0", "csv-writer": "1.6.0",
"probability-distributions": "0.9.1" "probability-distributions": "0.9.1"

View File

@ -19,7 +19,7 @@
"lib" "lib"
], ],
"devDependencies": { "devDependencies": {
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"c8": "8.0.1", "c8": "8.0.1",
"mocha": "10.2.0", "mocha": "10.2.0",
"should": "13.2.3" "should": "13.2.3"

View File

@ -23,7 +23,7 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"lodash": "4.17.21" "lodash": "4.17.21"
} }
} }

View File

@ -27,12 +27,12 @@
"dependencies": { "dependencies": {
"@tryghost/color-utils": "0.2.2", "@tryghost/color-utils": "0.2.2",
"@tryghost/email-events": "0.0.0", "@tryghost/email-events": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/html-to-plaintext": "0.0.0", "@tryghost/html-to-plaintext": "0.0.0",
"@tryghost/kg-default-cards": "10.0.6", "@tryghost/kg-default-cards": "10.0.6",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"@tryghost/validator": "0.2.11", "@tryghost/validator": "0.2.14",
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",
"cheerio": "0.22.0", "cheerio": "0.22.0",
"handlebars": "4.7.8", "handlebars": "4.7.8",

View File

@ -36,7 +36,7 @@
"@nestjs/common": "10.3.10", "@nestjs/common": "10.3.10",
"@nestjs/core": "10.3.10", "@nestjs/core": "10.3.10",
"@nestjs/platform-express": "10.3.10", "@nestjs/platform-express": "10.3.10",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",
"express": "4.19.2", "express": "4.19.2",
"reflect-metadata": "0.1.14", "reflect-metadata": "0.1.14",

View File

@ -21,7 +21,7 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/kg-default-cards": "10.0.6", "@tryghost/kg-default-cards": "10.0.6",
"@tryghost/string": "0.2.12", "@tryghost/string": "0.2.12",
"lodash": "4.17.21", "lodash": "4.17.21",

View File

@ -28,8 +28,8 @@
}, },
"dependencies": { "dependencies": {
"@breejs/later": "4.2.0", "@breejs/later": "4.2.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"bree": "6.5.0", "bree": "6.5.0",
"cron-validate": "1.4.5", "cron-validate": "1.4.5",
"fastq": "1.17.1", "fastq": "1.17.1",

View File

@ -24,10 +24,10 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/link-redirects": "0.0.0", "@tryghost/link-redirects": "0.0.0",
"@tryghost/nql": "0.12.3", "@tryghost/nql": "0.12.3",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",
"lodash": "4.17.21", "lodash": "4.17.21",
"moment": "2.29.4" "moment": "2.29.4"

View File

@ -26,9 +26,9 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"@tryghost/validator": "0.2.11", "@tryghost/validator": "0.2.14",
"jsonwebtoken": "8.5.1" "jsonwebtoken": "8.5.1"
} }
} }

View File

@ -25,9 +25,9 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/in-memory-repository": "0.0.0", "@tryghost/in-memory-repository": "0.0.0",
"@tryghost/tpl": "0.1.30" "@tryghost/tpl": "0.1.32"
}, },
"c8": { "c8": {
"exclude": [ "exclude": [

View File

@ -24,8 +24,8 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/metrics": "1.0.34", "@tryghost/metrics": "1.0.34",
"form-data": "4.0.0", "form-data": "4.0.0",
"lodash": "4.17.21", "lodash": "4.17.21",

View File

@ -31,14 +31,14 @@
}, },
"dependencies": { "dependencies": {
"@tryghost/domain-events": "0.0.0", "@tryghost/domain-events": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/magic-link": "0.0.0", "@tryghost/magic-link": "0.0.0",
"@tryghost/member-events": "0.0.0", "@tryghost/member-events": "0.0.0",
"@tryghost/members-payments": "0.0.0", "@tryghost/members-payments": "0.0.0",
"@tryghost/nql": "0.12.3", "@tryghost/nql": "0.12.3",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"@tryghost/validator": "0.2.11", "@tryghost/validator": "0.2.14",
"@types/jsonwebtoken": "9.0.6", "@types/jsonwebtoken": "9.0.6",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",

View File

@ -26,8 +26,8 @@
}, },
"dependencies": { "dependencies": {
"@tryghost/domain-events": "0.0.0", "@tryghost/domain-events": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/member-events": "0.0.0", "@tryghost/member-events": "0.0.0",
"moment-timezone": "0.5.34" "moment-timezone": "0.5.34"
} }

View File

@ -25,11 +25,11 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/members-csv": "0.0.0", "@tryghost/members-csv": "0.0.0",
"@tryghost/metrics": "1.0.34", "@tryghost/metrics": "1.0.34",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"moment-timezone": "0.5.45" "moment-timezone": "0.5.45"
} }
} }

View File

@ -26,8 +26,8 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"cookies": "0.9.1", "cookies": "0.9.1",
"jsonwebtoken": "8.5.1" "jsonwebtoken": "8.5.1"
} }

View File

@ -23,7 +23,7 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"bson-objectid": "2.0.4" "bson-objectid": "2.0.4"
} }
} }

View File

@ -24,9 +24,9 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"csso": "5.0.5", "csso": "5.0.5",
"terser": "5.31.3", "terser": "5.31.3",
"tiny-glob": "0.2.9" "tiny-glob": "0.2.9"

View File

@ -18,7 +18,7 @@
"lib" "lib"
], ],
"devDependencies": { "devDependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"c8": "8.0.1", "c8": "8.0.1",
"mocha": "10.2.0", "mocha": "10.2.0",
"sinon": "15.2.0" "sinon": "15.2.0"

View File

@ -23,10 +23,10 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/http-cache-utils": "0.1.15", "@tryghost/http-cache-utils": "0.1.17",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"lodash": "4.17.21", "lodash": "4.17.21",
"semver": "7.6.3" "semver": "7.6.3"
} }

View File

@ -23,8 +23,8 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"semver": "7.6.3" "semver": "7.6.3"
} }
} }

View File

@ -23,9 +23,9 @@
}, },
"dependencies": { "dependencies": {
"@extractus/oembed-extractor": "3.2.1", "@extractus/oembed-extractor": "3.2.1",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"charset": "1.0.1", "charset": "1.0.1",
"cheerio": "0.22.0", "cheerio": "0.22.0",
"iconv-lite": "0.6.3", "iconv-lite": "0.6.3",

View File

@ -26,7 +26,7 @@
}, },
"dependencies": { "dependencies": {
"@tryghost/domain-events": "0.0.0", "@tryghost/domain-events": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/mongo-utils": "0.6.2", "@tryghost/mongo-utils": "0.6.2",
"@tryghost/string": "0.2.12", "@tryghost/string": "0.2.12",
"lodash": "4.17.21" "lodash": "4.17.21"

View File

@ -25,8 +25,8 @@
"tmp": "0.2.1" "tmp": "0.2.1"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"lodash": "4.17.21" "lodash": "4.17.21"
} }

View File

@ -25,7 +25,7 @@
}, },
"dependencies": { "dependencies": {
"@tryghost/domain-events": "0.0.0", "@tryghost/domain-events": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/members-offers": "0.0.0", "@tryghost/members-offers": "0.0.0",
"@tryghost/tiers": "0.0.0" "@tryghost/tiers": "0.0.0"
} }

View File

@ -23,10 +23,10 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/nql": "0.12.3", "@tryghost/nql": "0.12.3",
"@tryghost/post-events": "0.0.0", "@tryghost/post-events": "0.0.0",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"bson-objectid": "2.0.4" "bson-objectid": "2.0.4"
} }
} }

View File

@ -30,10 +30,10 @@
"typescript": "5.4.5" "typescript": "5.4.5"
}, },
"dependencies": { "dependencies": {
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/in-memory-repository": "0.0.0", "@tryghost/in-memory-repository": "0.0.0",
"@tryghost/bookshelf-repository": "0.0.0", "@tryghost/bookshelf-repository": "0.0.0",
"@tryghost/logging": "2.4.15" "@tryghost/logging": "2.4.18"
} }
} }

View File

@ -25,6 +25,6 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2" "@tryghost/errors": "1.3.5"
} }
} }

View File

@ -24,8 +24,8 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"date-fns": "2.30.0" "date-fns": "2.30.0"
} }
} }

View File

@ -23,9 +23,9 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/validator": "0.2.11", "@tryghost/validator": "0.2.14",
"@tryghost/version": "0.1.28", "@tryghost/version": "0.1.30",
"got": "9.6.0" "got": "9.6.0"
} }
} }

View File

@ -4,7 +4,7 @@ const {MilestoneCreatedEvent} = require('@tryghost/milestones');
// @NOTE: 'StaffService' is a vague name that does not describe what it's actually doing. // @NOTE: 'StaffService' is a vague name that does not describe what it's actually doing.
// Possibly, "StaffNotificationService" or "StaffEventNotificationService" would be a more accurate name // Possibly, "StaffNotificationService" or "StaffEventNotificationService" would be a more accurate name
class StaffService { class StaffService {
constructor({logging, models, mailer, settingsCache, settingsHelpers, urlUtils, DomainEvents, labs, memberAttributionService}) { constructor({logging, models, mailer, settingsCache, settingsHelpers, urlUtils, blogIcon, DomainEvents, labs, memberAttributionService}) {
this.logging = logging; this.logging = logging;
this.labs = labs; this.labs = labs;
/** @private */ /** @private */
@ -22,6 +22,7 @@ class StaffService {
settingsHelpers, settingsHelpers,
settingsCache, settingsCache,
urlUtils, urlUtils,
blogIcon,
labs labs
}); });
} }

View File

@ -5,12 +5,13 @@ const glob = require('glob');
const {EmailAddressParser} = require('@tryghost/email-addresses'); const {EmailAddressParser} = require('@tryghost/email-addresses');
class StaffServiceEmails { class StaffServiceEmails {
constructor({logging, models, mailer, settingsHelpers, settingsCache, urlUtils, labs}) { constructor({logging, models, mailer, settingsHelpers, settingsCache, blogIcon, urlUtils, labs}) {
this.logging = logging; this.logging = logging;
this.models = models; this.models = models;
this.mailer = mailer; this.mailer = mailer;
this.settingsHelpers = settingsHelpers; this.settingsHelpers = settingsHelpers;
this.settingsCache = settingsCache; this.settingsCache = settingsCache;
this.blogIcon = blogIcon;
this.urlUtils = urlUtils; this.urlUtils = urlUtils;
this.labs = labs; this.labs = labs;
@ -44,6 +45,7 @@ class StaffServiceEmails {
attributionUrl: attribution?.url || '', attributionUrl: attribution?.url || '',
referrerSource: attribution?.referrerSource, referrerSource: attribution?.referrerSource,
siteTitle: this.settingsCache.get('title'), siteTitle: this.settingsCache.get('title'),
siteIconUrl: this.blogIcon.getIconUrl(true),
siteUrl: this.urlUtils.getSiteUrl(), siteUrl: this.urlUtils.getSiteUrl(),
siteDomain: this.siteDomain, siteDomain: this.siteDomain,
accentColor: this.settingsCache.get('accent_color'), accentColor: this.settingsCache.get('accent_color'),
@ -103,6 +105,7 @@ class StaffServiceEmails {
offerData, offerData,
subscriptionData, subscriptionData,
siteTitle: this.settingsCache.get('title'), siteTitle: this.settingsCache.get('title'),
siteIconUrl: this.blogIcon.getIconUrl(true),
siteUrl: this.urlUtils.getSiteUrl(), siteUrl: this.urlUtils.getSiteUrl(),
siteDomain: this.siteDomain, siteDomain: this.siteDomain,
accentColor: this.settingsCache.get('accent_color'), accentColor: this.settingsCache.get('accent_color'),
@ -153,6 +156,7 @@ class StaffServiceEmails {
tierData, tierData,
subscriptionData, subscriptionData,
siteTitle: this.settingsCache.get('title'), siteTitle: this.settingsCache.get('title'),
siteIconUrl: this.blogIcon.getIconUrl(true),
siteUrl: this.urlUtils.getSiteUrl(), siteUrl: this.urlUtils.getSiteUrl(),
siteDomain: this.siteDomain, siteDomain: this.siteDomain,
accentColor: this.settingsCache.get('accent_color'), accentColor: this.settingsCache.get('accent_color'),
@ -182,6 +186,7 @@ class StaffServiceEmails {
return { return {
siteTitle: this.settingsCache.get('title'), siteTitle: this.settingsCache.get('title'),
siteIconUrl: this.blogIcon.getIconUrl(true),
siteUrl: this.urlUtils.getSiteUrl(), siteUrl: this.urlUtils.getSiteUrl(),
siteDomain: this.siteDomain, siteDomain: this.siteDomain,
accentColor: this.settingsCache.get('accent_color'), accentColor: this.settingsCache.get('accent_color'),
@ -282,6 +287,7 @@ class StaffServiceEmails {
const templateData = { const templateData = {
siteTitle: this.settingsCache.get('title'), siteTitle: this.settingsCache.get('title'),
siteUrl: this.urlUtils.getSiteUrl(), siteUrl: this.urlUtils.getSiteUrl(),
siteIconUrl: this.blogIcon.getIconUrl(true),
siteDomain: this.siteDomain, siteDomain: this.siteDomain,
fromEmail: this.fromEmailAddress, fromEmail: this.fromEmailAddress,
toEmail: to, toEmail: to,

View File

@ -21,21 +21,33 @@
<tr> <tr>
<td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;"> <td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;"> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
{{#if siteIconUrl}}
<tr>
<td align="center" style="padding-bottom: 56px; text-align: center;"><a href="{{siteUrl}}"><img src="{{siteIconUrl}}" alt="{{siteTitle}}" border="0" width="48" height="48"></a></td>
</tr>
{{/if}}
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
<h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; margin-bottom: 15px;">Cha-ching! 💰 You received {{donation.amount}} from {{donation.name}}.</h1> <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 24px;">Cha-ching! You received a {{donation.amount}} tip from {{donation.name}}.</h1>
<table width="100" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;"> <table width="100" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F4F5F6; border-radius: 8px;">
<tbody> <tbody>
<tr> <tr>
<td align="center" style="padding: 32px 24px; text-align: center;"> <td align="center" style="padding: 24px; text-align: center;">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 26px; padding: 0; text-align: left; margin: 0; color: #15171A; font-weight: 400;">Type:</p> <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 26px; padding: 0; text-align: left; margin: 0; color: #15171A; font-weight: 400;">From:</p>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 17px; line-height: 26px; padding: 0; text-align: left; margin: 0; margin-bottom: 20px; color: #15171A; font-weight: 700;">One-time payment</p> <p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; line-height: 26px; padding: 0; text-align: left; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 700;">{{donation.name}} (<span style="color:{{accentColor}}; text-decoration: none;">{{donation.email}}</span>)</p>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 26px; padding: 0; text-align: left; margin: 0; color: #15171A; font-weight: 400;">From:</p> <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 26px; padding: 0; text-align: left; margin: 0; color: #15171A; font-weight: 400;">Amount received:</p>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 17px; line-height: 26px; padding: 0; text-align: left; margin: 0; margin-bottom: 20px; color: #15171A; font-weight: 700;">{{#if memberData}}<a style="color:{{accentColor}}" href="{{memberData.adminUrl}}">{{donation.name}} ({{donation.email}})</a>{{else}}{{donation.name}} ({{donation.email}}){{/if}}</p> <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; line-height: 26px; padding: 0; text-align: left; margin: 0; color: #15171A; font-weight: 700;">{{donation.amount}}</p>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 26px; padding: 0; text-align: left; margin: 0; color: #15171A; font-weight: 400;">Amount received:</p> </td>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 17px; line-height: 26px; padding: 0; text-align: left; margin: 0; color: #15171A; font-weight: 700;">{{donation.amount}}</p> </tr>
</td> <tr>
</tr> <td style="padding:0 24px 24px;">
{{#if memberData}}
<a href="{{memberData.adminUrl}}" style="border:solid 1px {{accentColor}};border-radius:8px;box-sizing:border-box;display:inline-block;font-size:15px;font-weight:normal;margin:0;padding:10px 20px;text-decoration:none;background-color:{{accentColor}};border-color:{{accentColor}};color:#ffffff">View member</a>
{{else}}
<a href="{{adminUrl}}" style="border:solid 1px {{accentColor}};border-radius:8px;box-sizing:border-box;display:inline-block;font-size:15px;font-weight:normal;margin:0;padding:10px 20px;text-decoration:none;background-color:{{accentColor}};border-color:{{accentColor}};color:#ffffff">View dashboard</a>
{{/if}}
</td>
</tr>
</tbody> </tbody>
</table> </table>
</td> </td>
@ -43,13 +55,13 @@
<!-- START FOOTER --> <!-- START FOOTER -->
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 80px;"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top; padding-top: 56px;">
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{toEmail}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{toEmail}}</a></p> <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{toEmail}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{toEmail}}</a></p>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top;">
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">Dont want to receive these emails? Manage your preferences <a class="small" href="{{staffUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">here</a>.</p> <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">Dont want to receive these emails? Manage your preferences <a class="small" href="{{staffUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">here</a>.</p>
</td> </td>
</tr> </tr>

View File

@ -26,64 +26,46 @@
<tr> <tr>
<td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;"> <td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;"> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
{{#if siteIconUrl}}
<tr>
<td align="center" style="padding-bottom: 56px; text-align: center;"><a href="{{siteUrl}}"><img src="{{siteIconUrl}}" alt="{{siteTitle}}" border="0" width="48" height="48"></a></td>
</tr>
{{/if}}
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Congratulations!</p> <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 24px;">You have a new free member</h1>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">You have a <span style="font-weight: bold; color: #15212A;">new free member</span>.</p> <table width="100" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F4F5F6; border-radius: 8px;">
<table width="100" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
<tbody>
<tr>
<td align="left" style="padding: 16px;">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td style="padding-right: 14px; background-color: #F9F9FA; text-align: left; vertical-align: middle;" valign="middle">
<div style="width: 48px; height: 48px; background-color: #15171A; border-radius: 999px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 19px; color: #FFFFFF; text-align: center; vertical-align: center; font-weight: 500; line-height: 47px;">
{{memberData.initials}}
</div>
</td>
<td style="padding-right: 8px; background-color: #F9F9FA; text-align: left; vertical-align: middle;" valign="middle">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600;">{{memberData.name}}</p>
{{#if memberData.showEmail}}
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #394047; font-weight: 400;">{{memberData.email}}</p>
{{/if}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Created on {{memberData.createdAt}}{{#if memberData.location}} &#8226; {{memberData.location}} {{/if}}
</p>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
<tr>
<td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-top: 32px; padding-bottom: 12px;">
{{#if referrerSource}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Signup info</p>
<hr style="border-bottom: 1px solid #F4F4F5; margin-top: 4px; margin-bottom: 8px;" />
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600; padding-bottom: 4px;">Source
<span style="font-weight: normal; color:#3A464C;"> - {{referrerSource}}</span>
</p>
{{#if attributionTitle}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600;">Page
<span style="font-weight: normal; color:#3A464C;"> - <a href="{{attributionUrl}}" style="font-weight: normal; color:#3A464C;text-decoration:none">{{attributionTitle}}</a></span>
</p>
{{/if}}
{{/if}}
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
<tbody> <tbody>
<tr> <tr>
<td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-top: 32px; padding-bottom: 12px;"> <td align="left" style="padding: 24px;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;"> <table border="0" cellpadding="0" cellspacing="0">
<tr>
<td style="padding-right: 8px; background-color: #F4F5F6; text-align: left; vertical-align: middle;" valign="middle">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Name:</p>
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;">{{memberData.name}}{{#if memberData.showEmail}} ({{memberData.email}}){{/if}}</p>
{{#if referrerSource}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Source:</p>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;">{{referrerSource}}</p>
{{#if attributionTitle}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Page:</p>
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;"><a href="{{attributionUrl}}">{{attributionTitle}}</a></p>
{{/if}}
{{/if}}
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
<tbody> <tbody>
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; background-color: {{accentColor}}; border-radius: 5px; text-align: center;"> <a href="{{memberData.adminUrl}}" target="_blank" style="display: inline-block; color: #ffffff; background-color: {{accentColor}}; border: solid 1px {{accentColor}}; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: normal; margin: 0; padding: 9px 22px 10px; border-color: {{accentColor}};">View member</a></td> <td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
<tbody>
<tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; vertical-align: top; background-color: {{accentColor}}; border-radius: 8px; text-align: center;"> <a href="{{memberData.adminUrl}}" target="_blank" style="display: inline-block; color: #ffffff; background-color: {{accentColor}}; border: solid 1px {{accentColor}}; border-radius: 8px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 15px; font-weight: normal; margin: 0; padding: 10px 20px; border-color: {{accentColor}};">View member</a></td>
</tr>
</tbody>
</table>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -91,21 +73,29 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<hr/>
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
<p class="text-link" style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top:0; color: #3A464C;">{{memberData.adminUrl}}</p> <tbody>
<tr>
<td align="left" style="padding-top: 24px;">
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #7c8b9a; font-weight: normal; margin: 0; line-height: 25px;">Or copy and paste this URL into your browser:</p>
<p class="text-link" style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin:0; color: #7c8b9a; font-weight: normal;">{{memberData.adminUrl}}</p>
</td>
</tr>
</tbody>
</table>
</td> </td>
</tr> </tr>
<!-- START FOOTER --> <!-- START FOOTER -->
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 80px;"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top; padding-top: 56px;">
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{toEmail}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{toEmail}}</a></p> <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{toEmail}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{toEmail}}</a></p>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top;">
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">Dont want to receive these emails? Manage your preferences <a class="small" href="{{staffUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">here</a>.</p> <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">Dont want to receive these emails? Manage your preferences <a class="small" href="{{staffUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">here</a>.</p>
</td> </td>
</tr> </tr>

View File

@ -26,77 +26,52 @@
<tr> <tr>
<td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;"> <td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;"> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
{{#if siteIconUrl}}
<tr>
<td align="center" style="padding-bottom: 56px; text-align: center;"><a href="{{siteUrl}}"><img src="{{siteIconUrl}}" alt="{{siteTitle}}" border="0" width="48" height="48"></a></td>
</tr>
{{/if}}
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Congratulations!</p> <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 24px;">You have a new paid subscriber</h1>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">You have a <span style="font-weight: bold; color: #15212A;">new paid member</span>.</p> <table width="100" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F4F5F6; border-radius: 8px;">
<table width="100" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
<tbody>
<tr>
<td align="left" style="padding: 16px;">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td style="padding-right: 14px; vertical-align: middle;" valign="middle">
<div style="width: 48px; height: 48px; background-color: #15171A; border-radius: 999px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 19px; color: #FFFFFF; text-align: center; vertical-align: center; font-weight: 500; line-height: 47px;">
{{memberData.initials}}
</div>
</td>
<td style="padding-right: 8px; vertical-align: middle;" valign="middle">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600;">{{memberData.name}}</p>
{{#if memberData.showEmail}}
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #394047; font-weight: 400;">{{memberData.email}}</p>
{{/if}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Subscription started on {{subscriptionData.startedOn}} </p>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
<tr>
<td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-top: 32px; padding-bottom: 12px;">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Tier</p>
<hr style="border-bottom: 1px solid #F4F4F5; margin-top: 4px; margin-bottom: 8px;" />
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600; padding-bottom: 32px;">{{tierData.name}}
{{#if tierData.details}} <span style="font-weight: normal; color:#3A464C;"> - {{tierData.details}}</span>{{/if}}
</p>
{{#if offerData}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Offer</p>
<hr style="border-bottom: 1px solid #F4F4F5; margin-top: 4px; margin-bottom: 8px;" />
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600; padding-bottom: 32px;">{{offerData.name}} <span style="font-weight: normal; color:#3A464C;"> - {{offerData.details}}</span></p>
{{/if}}
{{#if referrerSource}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 13px; padding-right: 8px; padding: 0; margin: 0; color: #95A1AD;">Signup info</p>
<hr style="border-bottom: 1px solid #F4F4F5; margin-top: 4px; margin-bottom: 8px;" />
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600; padding-bottom: 4px;">Source
<span style="font-weight: normal; color:#3A464C;"> - {{referrerSource}}</span>
</p>
{{#if attributionTitle}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; padding-right: 8px; padding: 0; margin: 0; color: #15171A; font-weight: 600;">Page
<span style="font-weight: normal; color:#3A464C;"> - <a href="{{attributionUrl}}" style="font-weight: normal; color:#3A464C;text-decoration:none">{{attributionTitle}}</a></span>
</p>
{{/if}}
{{/if}}
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
<tbody> <tbody>
<tr> <tr>
<td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-top: 32px; padding-bottom: 12px;"> <td align="left" style="padding: 24px;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;"> <table border="0" cellpadding="0" cellspacing="0">
<tr>
<td style="padding-right: 8px; background-color: #F4F5F6; text-align: left; vertical-align: middle;" valign="middle">
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Name:</p>
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;">{{memberData.name}}{{#if memberData.showEmail}} ({{memberData.email}}){{/if}}</p>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Tier:</p>
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;">{{tierData.name}}{{#if tierData.details}} &bull; {{tierData.details}}{{/if}}</p>
{{#if offerData}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Offer:</p>
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;">{{offerData.name}} &bull; <span style="color: {{accentColor}};">{{offerData.details}}</span></p>
{{/if}}
{{#if referrerSource}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Source:</p>
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;">{{referrerSource}}</p>
{{#if attributionTitle}}
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 400;">Page:</p>
<p class="text-link" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 600;"><a href="{{attributionUrl}}">{{attributionTitle}}</a></p>
{{/if}}
{{/if}}
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
<tbody> <tbody>
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; background-color: {{accentColor}}; border-radius: 5px; text-align: center;"> <a href="{{memberData.adminUrl}}" target="_blank" style="display: inline-block; color: #ffffff; background-color: {{accentColor}}; border: solid 1px {{accentColor}}; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: normal; margin: 0; padding: 9px 22px 10px; border-color: {{accentColor}};">View member</a> </td> <td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
<tbody>
<tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; vertical-align: top; background-color: {{accentColor}}; border-radius: 8px; text-align: center;"> <a href="{{memberData.adminUrl}}" target="_blank" style="display: inline-block; color: #ffffff; background-color: {{accentColor}}; border: solid 1px {{accentColor}}; border-radius: 8px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 15px; font-weight: normal; margin: 0; padding: 10px 20px; border-color: {{accentColor}};">View member</a></td>
</tr>
</tbody>
</table>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -104,21 +79,29 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<hr/>
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
<p class="text-link" style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top:0; color: #3A464C;">{{memberData.adminUrl}}</p> <tbody>
<tr>
<td align="left" style="padding-top: 24px;">
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #7c8b9a; font-weight: normal; margin: 0; line-height: 25px;">Or copy and paste this URL into your browser:</p>
<p class="text-link" style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin:0; color: #7c8b9a; font-weight: normal;">{{memberData.adminUrl}}</p>
</td>
</tr>
</tbody>
</table>
</td> </td>
</tr> </tr>
<!-- START FOOTER --> <!-- START FOOTER -->
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 80px;"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top; padding-top: 56px;">
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{toEmail}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{toEmail}}</a></p> <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{toEmail}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{toEmail}}</a></p>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top;">
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">Dont want to receive these emails? Manage your preferences <a class="small" href="{{staffUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">here</a>.</p> <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">Dont want to receive these emails? Manage your preferences <a class="small" href="{{staffUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">here</a>.</p>
</td> </td>
</tr> </tr>

View File

@ -78,10 +78,6 @@ function testCommonPaidSubMailData({member, mailStub, getEmailAlertUsersStub}) {
mailStub.calledWith( mailStub.calledWith(
sinon.match.has('html', sinon.match('$50.00/month')) sinon.match.has('html', sinon.match('$50.00/month'))
).should.be.true(); ).should.be.true();
mailStub.calledWith(
sinon.match.has('html', sinon.match('Subscription started on 1 Aug 2022'))
).should.be.true();
} }
function testCommonPaidSubCancelMailData({mailStub, getEmailAlertUsersStub}) { function testCommonPaidSubCancelMailData({mailStub, getEmailAlertUsersStub}) {
@ -149,6 +145,12 @@ describe('StaffService', function () {
} }
}; };
const blogIcon = {
getIconUrl: () => {
return 'https://ghost.example/siteicon.png';
}
};
const settingsHelpers = { const settingsHelpers = {
getDefaultEmailDomain: () => { getDefaultEmailDomain: () => {
return 'ghost.example'; return 'ghost.example';
@ -184,6 +186,7 @@ describe('StaffService', function () {
}, },
settingsCache, settingsCache,
urlUtils, urlUtils,
blogIcon,
settingsHelpers, settingsHelpers,
labs labs
}); });
@ -220,6 +223,7 @@ describe('StaffService', function () {
DomainEvents, DomainEvents,
settingsCache, settingsCache,
urlUtils, urlUtils,
blogIcon,
settingsHelpers settingsHelpers
}); });
service.subscribeEvents(); service.subscribeEvents();
@ -339,6 +343,7 @@ describe('StaffService', function () {
}, },
settingsCache, settingsCache,
urlUtils, urlUtils,
blogIcon,
settingsHelpers, settingsHelpers,
labs: { labs: {
isSet: () => { isSet: () => {
@ -430,9 +435,6 @@ describe('StaffService', function () {
mailStub.calledWith( mailStub.calledWith(
sinon.match.has('html', sinon.match('🥳 Free member signup: Ghost')) sinon.match.has('html', sinon.match('🥳 Free member signup: Ghost'))
).should.be.true(); ).should.be.true();
mailStub.calledWith(
sinon.match.has('html', sinon.match('Created on 1 Aug 2022 &#8226; France'))
).should.be.true();
}); });
it('sends free member signup alert without member name', async function () { it('sends free member signup alert without member name', async function () {
@ -455,18 +457,13 @@ describe('StaffService', function () {
mailStub.calledWith( mailStub.calledWith(
sinon.match.has('html', sinon.match('🥳 Free member signup: member@example.com')) sinon.match.has('html', sinon.match('🥳 Free member signup: member@example.com'))
).should.be.true(); ).should.be.true();
mailStub.calledWith(
sinon.match.has('html', sinon.match('Created on 1 Aug 2022 &#8226; France'))
).should.be.true();
}); });
it('sends free member signup alert with attribution', async function () { it('sends free member signup alert with attribution', async function () {
const member = { const member = {
name: 'Ghost', name: 'Ghost',
email: 'member@example.com', email: 'member@example.com',
id: 'abc', id: 'abc'
geolocation: '{"country": "France"}',
created_at: '2022-08-01T07:30:39.882Z'
}; };
const attribution = { const attribution = {
@ -487,9 +484,6 @@ describe('StaffService', function () {
mailStub.calledWith( mailStub.calledWith(
sinon.match.has('html', sinon.match('🥳 Free member signup: Ghost')) sinon.match.has('html', sinon.match('🥳 Free member signup: Ghost'))
).should.be.true(); ).should.be.true();
mailStub.calledWith(
sinon.match.has('html', sinon.match('Created on 1 Aug 2022 &#8226; France'))
).should.be.true();
mailStub.calledWith( mailStub.calledWith(
sinon.match.has('html', sinon.match('Source')) sinon.match.has('html', sinon.match('Source'))
@ -520,9 +514,7 @@ describe('StaffService', function () {
member = { member = {
name: 'Ghost', name: 'Ghost',
email: 'member@example.com', email: 'member@example.com',
id: 'abc', id: 'abc'
geolocation: '{"country": "France"}',
created_at: '2022-08-01T07:30:39.882Z'
}; };
offer = { offer = {
name: 'Half price', name: 'Half price',
@ -588,9 +580,7 @@ describe('StaffService', function () {
it('sends paid subscription start alert without member name', async function () { it('sends paid subscription start alert without member name', async function () {
let memberData = { let memberData = {
email: 'member@example.com', email: 'member@example.com',
id: 'abc', id: 'abc'
geolocation: '{"country": "France"}',
created_at: '2022-08-01T07:30:39.882Z'
}; };
await service.emails.notifyPaidSubscriptionStarted({member: memberData, offer: null, tier, subscription}, options); await service.emails.notifyPaidSubscriptionStarted({member: memberData, offer: null, tier, subscription}, options);

View File

@ -25,7 +25,7 @@
"@types/sinon": "10.0.16", "@types/sinon": "10.0.16",
"c8": "8.0.1", "c8": "8.0.1",
"knex": "2.4.2", "knex": "2.4.2",
"luxon": "3.4.4", "luxon": "3.5.0",
"mocha": "10.2.0", "mocha": "10.2.0",
"should": "13.2.3", "should": "13.2.3",
"sinon": "15.2.0", "sinon": "15.2.0",

View File

@ -25,10 +25,10 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/domain-events": "0.0.0", "@tryghost/domain-events": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/member-events": "0.0.0", "@tryghost/member-events": "0.0.0",
"leaky-bucket": "2.2.0", "leaky-bucket": "2.2.0",
"lodash": "4.17.21", "lodash": "4.17.21",

View File

@ -22,9 +22,9 @@
"mocha": "10.2.0" "mocha": "10.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/string": "0.2.12", "@tryghost/string": "0.2.12",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"bson-objectid": "2.0.4" "bson-objectid": "2.0.4"
} }
} }

View File

@ -25,10 +25,10 @@
"uuid": "9.0.1" "uuid": "9.0.1"
}, },
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.30", "@tryghost/debug": "0.1.32",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"@tryghost/tpl": "0.1.30", "@tryghost/tpl": "0.1.32",
"lodash": "4.17.21", "lodash": "4.17.21",
"moment": "2.24.0" "moment": "2.24.0"
} }

View File

@ -25,7 +25,7 @@
}, },
"dependencies": { "dependencies": {
"@tryghost/domain-events": "0.0.0", "@tryghost/domain-events": "0.0.0",
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/member-events": "0.0.0" "@tryghost/member-events": "0.0.0"
} }
} }

View File

@ -25,8 +25,8 @@
"sinon": "15.2.0" "sinon": "15.2.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"cheerio": "0.22.0" "cheerio": "0.22.0"
} }
} }

View File

@ -43,8 +43,8 @@
"prepare": "husky install .github/hooks" "prepare": "husky install .github/hooks"
}, },
"resolutions": { "resolutions": {
"@tryghost/errors": "1.3.2", "@tryghost/errors": "1.3.5",
"@tryghost/logging": "2.4.15", "@tryghost/logging": "2.4.18",
"jackspeak": "2.1.1", "jackspeak": "2.1.1",
"moment": "2.24.0", "moment": "2.24.0",
"moment-timezone": "0.5.45" "moment-timezone": "0.5.45"
@ -115,9 +115,9 @@
"eslint-plugin-ghost": "3.4.0", "eslint-plugin-ghost": "3.4.0",
"eslint-plugin-react": "7.33.0", "eslint-plugin-react": "7.33.0",
"husky": "8.0.3", "husky": "8.0.3",
"lint-staged": "15.2.7", "lint-staged": "15.2.8",
"nx": "16.8.1", "nx": "16.8.1",
"rimraf": "5.0.9", "rimraf": "5.0.10",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"typescript": "5.4.5", "typescript": "5.4.5",
"inquirer": "8.2.6" "inquirer": "8.2.6"

1989
yarn.lock

File diff suppressed because it is too large Load Diff