Merge branch 'main' into main

This commit is contained in:
Ryan Feigenbaum 2024-05-30 10:14:26 -04:00 committed by GitHub
commit 5ea5c35932
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 236 additions and 129 deletions

View File

@ -39,7 +39,7 @@
"dependencies": {
"@codemirror/lang-html": "6.4.9",
"@tryghost/color-utils": "0.2.2",
"@tryghost/kg-unsplash-selector": "0.1.17",
"@tryghost/kg-unsplash-selector": "0.2.0",
"@tryghost/limit-service": "1.2.14",
"@tryghost/nql": "0.12.3",
"@tryghost/timezone-data": "0.4.3",

View File

@ -63,14 +63,22 @@ const features = [{
title: 'Internal Linking (private beta)',
description: 'Adds internal URL search to editor link inputs',
flag: 'internalLinking'
},{
title: 'Internal Linking @-links (internal alpha)',
description: 'Adds internal URL search when typing @ in the editor',
flag: 'internalLinkingAtLinks'
},{
title: 'ActivityPub',
description: '(Highly) Experimental support for ActivityPub.',
flag: 'ActivityPub'
},{
title: 'Subhead',
description: 'Using custom excerpts as subheads in editor and newsletter',
flag: 'subhead'
title: 'Subtitle in editor',
description: 'Using custom excerpt as subtitle in editor',
flag: 'editorSubtitle'
},{
title: 'Subtitle in newsletter',
description: 'Showing subtitle in newsletter',
flag: 'newsletterSubtitle'
}];
const AlphaFeatures: React.FC = () => {

View File

@ -103,7 +103,7 @@ const Sidebar: React.FC<{
const {mutateAsync: uploadImage} = useUploadImage();
const [selectedTab, setSelectedTab] = useState('generalSettings');
const hasEmailCustomization = useFeatureFlag('emailCustomization');
const hasSubhead = useFeatureFlag('subhead');
const hasNewsletterSubtitle = useFeatureFlag('newsletterSubtitle');
const {localSettings} = useSettingGroup();
const [siteTitle] = getSettingValues(localSettings, ['title']) as string[];
const handleError = useHandleError();
@ -417,6 +417,22 @@ const Sidebar: React.FC<{
value={newsletter.title_color}
onChange={color => updateNewsletter({title_color: color})}
/>}
<ToggleGroup gap='lg'>
{(hasNewsletterSubtitle && newsletter.show_post_title_section) &&
<Toggle
checked={newsletter.show_subhead}
direction="rtl"
label='Subtitle'
onChange={e => updateNewsletter({show_subhead: e.target.checked})}
/>
}
<Toggle
checked={newsletter.show_feature_image}
direction="rtl"
label='Feature image'
onChange={e => updateNewsletter({show_feature_image: e.target.checked})}
/>
</ToggleGroup>
<Select
options={fontOptions}
selectedOption={fontOptions.find(option => option.value === newsletter.body_font_category)}
@ -424,20 +440,6 @@ const Sidebar: React.FC<{
title='Body style'
onSelect={option => updateNewsletter({body_font_category: option?.value})}
/>
{hasSubhead &&
<Toggle
checked={newsletter.show_subhead}
direction="rtl"
label='Subhead'
onChange={e => updateNewsletter({show_subhead: e.target.checked})}
/>
}
<Toggle
checked={newsletter.show_feature_image}
direction="rtl"
label='Feature image'
onChange={e => updateNewsletter({show_feature_image: e.target.checked})}
/>
</Form>
<Form className='mt-6' gap='sm' margins='lg' title='Footer'>

View File

@ -77,7 +77,7 @@ const NewsletterPreviewContent: React.FC<{
const showHeader = headerIcon || headerTitle;
const {config} = useGlobalData();
const hasNewEmailAddresses = useFeatureFlag('newEmailAddresses');
const hasSubhead = useFeatureFlag('subhead');
const hasNewsletterSubtitle = useFeatureFlag('newsletterSubtitle');
const currentDate = new Date().toLocaleDateString('default', {
year: 'numeric',
@ -115,11 +115,11 @@ const NewsletterPreviewContent: React.FC<{
<div className="border border-transparent px-16" style={{borderColor}}>
{headerImage && (
<div>
<img alt="" className="mt-6 block" src={headerImage} />
<img alt="" className="mb-4 mt-6 block" src={headerImage} />
</div>
)}
{showHeader && (
<div className="border-b border-grey-200 py-8" style={{borderColor: secondaryBorderColor}}>
<div className="py-3" style={{borderColor: secondaryBorderColor}}>
{headerIcon && <img alt="" className="mx-auto mb-2 h-10 w-10" role="presentation" src={headerIcon} />}
{headerTitle && <h4 className="mb-1 text-center text-[1.6rem] font-bold uppercase leading-tight tracking-tight text-grey-900" style={{color: textColor}}>{headerTitle}</h4>}
{headerSubtitle && <h5 className="mb-1 text-center text-md font-normal text-grey-700" style={{color: secondaryTextColor}}>{headerSubtitle}</h5>}
@ -134,8 +134,8 @@ const NewsletterPreviewContent: React.FC<{
)} style={{color: titleColor}}>
Your email newsletter
</h2>
{(hasSubhead && showSubhead) && (
<p className="mb-4 text-[1.6rem] leading-[1.7] text-black">A subhead that highlights the key points of your newsletter.</p>
{(hasNewsletterSubtitle && showSubhead) && (
<p className="mb-5 text-pretty text-[1.6rem] leading-[1.7] text-black">A subtitle can help highlight key points and engage your readers.</p>
)}
<div className={clsx(
'flex w-full justify-between text-center text-md leading-none text-grey-700',
@ -153,14 +153,21 @@ const NewsletterPreviewContent: React.FC<{
{/* Feature image */}
{showFeatureImage && (
<>
<div className="h-[300px] w-full max-w-[600px] bg-grey-200 bg-cover bg-no-repeat">
<div className={clsx(
'h-[300px] w-full max-w-[600px] bg-cover bg-no-repeat',
showPostTitleSection ? '' : 'pt-6'
)}>
<img alt="Feature" className='min-h-full min-w-full shrink-0' src={CoverImage} />
</div>
<div className="mt-1 w-full max-w-[600px] pb-8 text-center text-[1.3rem] text-grey-700" style={{color: secondaryTextColor}}>Feature image caption</div>
</>
)}
<div className={clsx('max-w-[600px] border-b border-grey-200 pb-5 text-[1.6rem] leading-[1.7] text-black', bodyFontCategory === 'serif' && 'font-serif')} style={{borderColor: secondaryBorderColor}}>
<div className={clsx(
'max-w-[600px] border-b border-grey-200 pb-5 text-[1.6rem] leading-[1.7] text-black',
bodyFontCategory === 'serif' && 'font-serif',
(showFeatureImage || showPostTitleSection) ? '' : 'pt-8'
)} style={{borderColor: secondaryBorderColor}}>
<p className="mb-5" style={{color: textColor}}>This is what your content will look like when you send one of your posts as an email newsletter to your subscribers.</p>
<p className="mb-5" style={{color: textColor}}>Over there on the right you&apos;ll see some settings that allow you to customize the look and feel of this template to make it perfectly suited to your brand. Email templates are exceptionally finnicky to make, but we&apos;ve spent a long time optimising this one to make it work beautifully across devices, email clients and content types.</p>
<p className="mb-5" style={{color: textColor}}>So, you can trust that every email you send with Ghost will look great and work well. Just like the rest of your site.</p>

View File

@ -261,13 +261,14 @@ export function getFreeProduct({
}
export function getBenefits({numOfBenefits}) {
const timestamp = Date.now();
const random = Math.floor(Math.random() * 100);
const beenfits = [
getBenefitData({name: `Limited early adopter pricing #${random}`}),
getBenefitData({name: `Latest gear reviews #${random}`}),
getBenefitData({name: `Weekly email newsletter #${random}`}),
getBenefitData({name: `Listen to my podcast #${random}`})
getBenefitData({name: `Limited early adopter pricing #${random}-${timestamp}`}),
getBenefitData({name: `Latest gear reviews #${random}-${timestamp}`}),
getBenefitData({name: `Weekly email newsletter #${random}-${timestamp}`}),
getBenefitData({name: `Listen to my podcast #$${random}-${timestamp}`})
];
return beenfits.slice(0, numOfBenefits);
}

View File

@ -57,10 +57,10 @@
data-test-editor-title-input={{true}}
/>
{{#if (feature 'subhead')}}
{{#if (feature 'editorSubtitle')}}
<GhTextarea
@class="gh-editor-subhead"
@placeholder="Add a subhead..."
@class="gh-editor-subtitle"
@placeholder="Add a post excerpt..."
@shouldFocus={{false}}
@tabindex="1"
@autoExpand=".gh-koenig-editor"

View File

@ -117,10 +117,10 @@ export default class GhKoenigEditorReactComponent extends Component {
// - Enter, creating an empty paragraph when editor is not empty
@action
onTitleKeydown(event) {
if (this.feature.get('subhead')) {
if (this.feature.get('editorSubtitle')) {
if (event.key === 'Enter') {
event.preventDefault();
const subheadElement = document.querySelector('.gh-editor-subhead');
const subheadElement = document.querySelector('.gh-editor-subtitle');
if (subheadElement) {
subheadElement.focus();
}
@ -150,7 +150,7 @@ export default class GhKoenigEditorReactComponent extends Component {
}
}
// Subheader ("excerpt") Actions -------------------------------------------
// Subhead ("excerpt") Actions -------------------------------------------
@action
updateExcerpt(event) {

View File

@ -93,7 +93,7 @@
{{/if}}
{{/if}}
{{#unless (feature 'subhead')}}
{{#unless (feature 'editorSubtitle')}}
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="customExcerpt">
<label for="custom-excerpt">Excerpt</label>
<GhTextarea

View File

@ -374,10 +374,18 @@ export default class KoenigLexicalEditor extends Component {
}
}
// only published posts/pages have URLs
// only published posts/pages and staff with posts have URLs
const filteredResults = [];
results.forEach((group) => {
const items = (group.groupName === 'Posts' || group.groupName === 'Pages') ? group.options.filter(i => i.status === 'published') : group.options;
let items = group.options;
if (group.groupName === 'Posts' || group.groupName === 'Pages') {
items = items.filter(i => i.status === 'published');
}
if (group.groupName === 'Staff') {
items = items.filter(i => !/\/404\//.test(i.url));
}
if (items.length === 0) {
return;
@ -417,7 +425,8 @@ export default class KoenigLexicalEditor extends Component {
feature: {
collectionsCard: this.feature.collectionsCard,
collections: this.feature.collections,
internalLinking: this.feature.internalLinking
internalLinking: this.feature.internalLinking,
internalLinkingAtLinks: this.feature.internalLinkingAtLinks
},
deprecated: {
headerV1: true // if false, shows header v1 in the menu

View File

@ -83,7 +83,9 @@ export default class FeatureService extends Service {
@feature('onboardingChecklist') onboardingChecklist;
@feature('ActivityPub') ActivityPub;
@feature('internalLinking') internalLinking;
@feature('subhead') subhead;
@feature('internalLinkingAtLinks') internalLinkingAtLinks;
@feature('editorSubtitle') editorSubtitle;
@feature('newsletterSubtitle') newsletterSubtitle;
_user = null;

View File

@ -744,6 +744,11 @@ input:focus,
/* Editor */
.gh-editor-title,
.gh-editor-subtitle {
background: var(--white);
}
.gh-editor-title::placeholder {
color: var(--midlightgrey-d2);
}

View File

@ -788,7 +788,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
height: 2.4rem;
}
.gh-editor-subhead {
.gh-editor-subtitle {
display: block;
width: 100%;
max-width: unset;
@ -806,7 +806,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
box-shadow: none;
}
.gh-editor-subhead:focus {
.gh-editor-subtitle:focus {
box-shadow: none !important;
border: none !important;
}

View File

@ -45,9 +45,9 @@
"@tryghost/color-utils": "0.2.2",
"@tryghost/ember-promise-modals": "2.0.1",
"@tryghost/helpers": "1.1.90",
"@tryghost/kg-clean-basic-html": "4.0.9",
"@tryghost/kg-clean-basic-html": "4.1.0",
"@tryghost/kg-converters": "1.0.4",
"@tryghost/koenig-lexical": "1.1.16",
"@tryghost/koenig-lexical": "1.2.2",
"@tryghost/limit-service": "1.2.14",
"@tryghost/members-csv": "0.0.0",
"@tryghost/nql": "0.12.3",

View File

@ -52,7 +52,9 @@ const ALPHA_FEATURES = [
'importMemberTier',
'lexicalIndicators',
'adminXDemo',
'subhead'
'editorSubtitle',
'newsletterSubtitle',
'internalLinkingAtLinks'
];
module.exports.GA_KEYS = [...GA_FEATURES];

View File

@ -102,9 +102,9 @@
"@tryghost/kg-converters": "1.0.4",
"@tryghost/kg-default-atoms": "5.0.3",
"@tryghost/kg-default-cards": "10.0.5",
"@tryghost/kg-default-nodes": "1.0.16",
"@tryghost/kg-html-to-lexical": "1.0.17",
"@tryghost/kg-lexical-html-renderer": "1.0.16",
"@tryghost/kg-default-nodes": "1.1.0",
"@tryghost/kg-html-to-lexical": "1.1.1",
"@tryghost/kg-lexical-html-renderer": "1.1.1",
"@tryghost/kg-mobiledoc-html-renderer": "7.0.4",
"@tryghost/limit-service": "1.2.14",
"@tryghost/link-redirects": "0.0.0",
@ -207,7 +207,7 @@
"moment": "2.24.0",
"moment-timezone": "0.5.23",
"multer": "1.4.4",
"mysql2": "3.9.8",
"mysql2": "3.9.9",
"nconf": "0.12.1",
"node-jose": "2.2.0",
"path-match": "1.2.4",
@ -225,7 +225,7 @@
},
"optionalDependencies": {
"@sentry/profiling-node": "7.116.0",
"@tryghost/html-to-mobiledoc": "3.0.10",
"@tryghost/html-to-mobiledoc": "3.1.0",
"sqlite3": "5.1.7"
},
"devDependencies": {

View File

@ -169,7 +169,11 @@ describe('Can send cards via email', function () {
'extended-text', // not a card
'extended-quote', // not a card
'extended-heading', // not a card
'tk' // shouldn't be present in published posts / emails
// not a card and shouldn't be present in published posts / emails
'tk',
'at-link',
'at-link-search',
'zwnj'
];
const cardsInDefaultNodes = DEFAULT_NODES.map((node) => {

View File

@ -6,9 +6,16 @@ let tiers = {};
let models = {};
const crypto = require('crypto');
// @ts-check
/**
* @typedef {"customers" | "checkout" | "subscriptions" | "coupons" | "payment_methods" | "prices" | "setup_intents" | "products"} resource
*/
/**
* The Stripe Mocker mimics an in memory version of the Stripe API. We can use it to quickly create new subscriptions and get a close to real world test environment with working webhooks etc.
* If you create a new subscription, it will automatically send the customer.subscription.created webhook. So you can test what happens.
*
* If you create a new subscription, it will by default send the checkout.session.completed webhook.
* If you update a subscription, it will by default send the customer.subscription.updated webhook.
*/
class StripeMocker {
customers = [];
@ -49,10 +56,19 @@ class StripeMocker {
models = require('../../core/server/models');
}
/**
* Generates a random ID.
* @returns {string} The generated random ID.
*/
#generateRandomId() {
return crypto.randomBytes(8).toString('hex');
}
/**
* Creates a mock Stripe customer.
* @param {object} overrides - Optional overrides for the customer object.
* @returns {object} - The created Stripe customer object.
*/
createCustomer(overrides = {}) {
const customerId = `cus_${this.#generateRandomId()}`;
const stripeCustomer = {
@ -71,10 +87,10 @@ class StripeMocker {
}
/**
*
* @param {*} tierSlug
* Fetches a mock Stripe price for a given product/tier (by slug).
* @param {string} tierSlug
* @param {'month' | 'year'} cadence
* @returns
* @returns {Promise<object>} - The fetched Stripe price object.
*/
async getPriceForTier(tierSlug, cadence) {
const product = await models.Product.findOne({slug: tierSlug});
@ -89,13 +105,13 @@ class StripeMocker {
}
/**
*
* Creates a Ghost tier (product).
* @param {object} data
* @param {string} [data.name]
* @param {string} data.currency
* @param {number} data.monthly_price
* @param {number} data.yearly_price
* @returns
* @returns {Promise<object>} - The created product object.
*/
async createTier({name, currency, monthly_price, yearly_price}) {
const result = await tiers.api.add({
@ -110,6 +126,15 @@ class StripeMocker {
});
}
/**
* Creates a trial subscription.
*
* @param {object} options - The options for creating the trial subscription.
* @param {string} options.customer - The customer ID.
* @param {string} options.price - The price ID.
* @param {object} [options.overrides] - Optional overrides for the trial subscription.
* @returns {Promise<object>} - The created trial subscription object.
*/
async createTrialSubscription({customer, price, ...overrides}) {
return await this.createSubscription({
customer,
@ -143,6 +168,17 @@ class StripeMocker {
await DomainEvents.allSettled();
}
/**
* Creates a subscription for a customer.
*
* @param {object} data - The data for creating the subscription.
* @param {object} data.customer - The customer object.
* @param {object} data.price - The price object.
* @param {object} [data.overrides] - Optional overrides for the subscription object.
* @param {object} options - The options for creating the subscription.
* @param {object} [options.sendWebhook=true] - Whether to send a webhook or not.
* @returns {Promise<Object>} The created subscription object.
*/
async createSubscription({customer, price, ...overrides}, options = {sendWebhook: true}) {
const subscriptionId = `sub_${this.#generateRandomId()}`;
@ -188,6 +224,14 @@ class StripeMocker {
return subscription;
}
/**
* Retrieves requested data from the in-memory storage similar to a HTTP request.
*
* @param {Array} arr - The array to search in.
* @param {string} id - The id to search for.
* @returns {Array} - An array containing the HTTP status code and the setup intent object.
* If the setup intent is not found, returns [404].
*/
#getData(arr, id) {
const setupIntent = arr.find(c => c.id === id);
if (!setupIntent) {
@ -196,6 +240,15 @@ class StripeMocker {
return [200, setupIntent];
}
/**
* Handles creation and updating of in memory resources.
*
* @param {Array} arr - The array to store the processed data.
* @param {string} id - The ID of the resource.
* @param {string} body - The body of the POST request.
* @param {resource} resource - The type of resource.
* @returns {Array} - An array containing the HTTP status code and the processed data.
*/
#postData(arr, id, body, resource) {
const qs = require('qs');
let decoded = qs.parse(body, {
@ -309,6 +362,9 @@ class StripeMocker {
return [200, subscription];
}
/**
* Removes all the registered nock interceptors.
*/
remove() {
for (const interceptor of this.nockInterceptors) {
nock.removeInterceptor(interceptor);
@ -316,6 +372,10 @@ class StripeMocker {
this.nockInterceptors = [];
}
/**
* Stubs the Stripe API.
* This will intercept all requests to the Stripe API and return the mocked data.
*/
stub() {
this.remove();
@ -425,10 +485,13 @@ class StripeMocker {
});
}
/**
* Sends a webhook event to the Stripe webhook controller.
*
* @param {any} event - The webhook event to send.
* @returns {Promise<void>} - A promise that resolves when the webhook event is handled.
*/
async sendWebhook(event) {
/**
* @type {any}
*/
const webhookController = stripeService.webhookController;
await webhookController.handleEvent(event);
}

View File

@ -1079,7 +1079,7 @@ class EmailRenderer {
classes: {
title: 'post-title' + (newsletter.get('title_font_category') === 'serif' ? ` post-title-serif` : ``) + (newsletter.get('title_alignment') === 'left' ? ` post-title-left` : ``),
titleLink: 'post-title-link' + (newsletter.get('title_alignment') === 'left' ? ` post-title-link-left` : ``),
subhead: 'post-subhead' + (newsletter.get('title_alignment') === 'left' ? ` post-subhead-left` : ``),
subtitle: 'post-subtitle' + (newsletter.get('title_alignment') === 'left' ? ` post-subtitle-left` : ``),
meta: 'post-meta' + (newsletter.get('title_alignment') === 'left' ? ` post-meta-left` : ` post-meta-center`),
body: newsletter.get('body_font_category') === 'sans_serif' ? `post-content-sans-serif` : `post-content`
},

View File

@ -308,6 +308,7 @@ figure blockquote p {
.header-image {
padding-top: 24px;
padding-bottom: 16px;
}
.site-icon {
@ -376,12 +377,12 @@ figure blockquote p {
text-align: left;
}
.post-subhead-wrapper {
.post-subtitle-wrapper {
width: 100%;
max-width: 600px !important;
}
.post-subhead {
.post-subtitle {
margin: 0;
padding-bottom: 20px;
color: #15212A;
@ -390,7 +391,7 @@ figure blockquote p {
text-align: center;
}
.post-subhead-left {
.post-subtitle-left {
text-align: left;
}
@ -434,15 +435,17 @@ figure blockquote p {
text-decoration: underline !important;
}
.feature-image-row:first-child > td {
padding-top: 24px;
}
.feature-image {
padding-bottom: 30px;
width: 100%;
}
.feature-image-row:first-child > td,
.header-image-row + .feature-image-row > td,
.site-info-row + .feature-image-row > td {
padding-top: 24px;
}
.feature-image-with-caption {
width: 100%;
padding: 0;
@ -609,7 +612,7 @@ figure blockquote p {
}
.post-content-row:first-child > td, .header-image-row + .post-content-row > td {
padding-top: 52px;
padding-top: 32px;
}
.post-content a,
@ -1300,7 +1303,7 @@ a[data-flickr-embed] img {
font-size: 16px;
}
table.body .post-subhead {
table.body .post-subtitle {
font-size: 16px !important;
}

View File

@ -44,7 +44,7 @@
{{/if}}
{{#if (or showHeaderIcon showHeaderTitle showHeaderName) }}
<tr>
<tr class="site-info-row">
<td class="site-info" width="100%" align="center">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
{{#if (and showHeaderIcon site.iconUrl) }}
@ -79,11 +79,11 @@
<a href="{{post.url}}" class="{{classes.titleLink}}">{{post.title}}</a>
</td>
</tr>
{{#hasFeature 'subhead'}}
{{#hasFeature 'newsletterSubtitle'}}
{{#if newsletter.showSubhead}}
<tr>
<td class="post-subhead-wrapper" style="width: 100%">
<p class="{{classes.subhead}}">Jonathan Haidt wrote a best-selling book about teens and social media. Not everyone buys its thesis.</p>
<td class="post-subtitle-wrapper" style="width: 100%">
<p class="{{classes.subtitle}}">Jonathan Haidt wrote a best-selling book about teens and social media. Not everyone buys its thesis.</p>
</td>
</tr>
{{/if}}

View File

@ -1995,7 +1995,7 @@ describe('Email renderer', function () {
title: 'post-title post-title-serif post-title-left',
titleLink: 'post-title-link post-title-link-left',
meta: 'post-meta post-meta-left',
subhead: 'post-subhead post-subhead-left',
subtitle: 'post-subtitle post-subtitle-left',
body: 'post-content-sans-serif'
});
});

107
yarn.lock
View File

@ -6967,12 +6967,12 @@
dependencies:
lodash-es "^4.17.11"
"@tryghost/html-to-mobiledoc@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@tryghost/html-to-mobiledoc/-/html-to-mobiledoc-3.0.10.tgz#981a50a9bcdbe14d30beaf6a132921238fb9fa57"
integrity sha512-ThK2wFyH/6J01clhumR8BFILwDpbY3Epr0GYMP3RMhw2/hk1mf5xj0y9acdgFACBoTYzclOcqUt67CZY0/Lt3Q==
"@tryghost/html-to-mobiledoc@3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@tryghost/html-to-mobiledoc/-/html-to-mobiledoc-3.1.0.tgz#64cb333c8f0b857f53edf289867a39d6113ccefb"
integrity sha512-CL1IAL3mRrWPs2LcDcPptN2O2w9EY0mb/ofkOycQYaHoPPt0JgPOPeJdmIwRfddzGkEK1WA1yWnqKa4zSyIGRw==
dependencies:
"@tryghost/kg-parser-plugins" "4.0.9"
"@tryghost/kg-parser-plugins" "4.1.0"
"@tryghost/mobiledoc-kit" "^0.12.4-ghost.1"
jsdom "^24.0.0"
@ -7014,10 +7014,10 @@
resolved "https://registry.yarnpkg.com/@tryghost/kg-card-factory/-/kg-card-factory-5.0.4.tgz#b2de98eaf01edbd5629fb1f4b06eca3a5f95d0ad"
integrity sha512-KcNM4QJONSSOJeQlv9no5wFx+uV2mESX3bYBL2y3c0DqB26NlMaUx0QIAFSbCSinUlCvRFOwEEBQyaACtCOvzQ==
"@tryghost/kg-clean-basic-html@4.0.9":
version "4.0.9"
resolved "https://registry.yarnpkg.com/@tryghost/kg-clean-basic-html/-/kg-clean-basic-html-4.0.9.tgz#6ac4119a7f2fb1000d00a1559a6fe39e61f14848"
integrity sha512-VwVpFwlf/mVghbCQksVSuQRnixunJZMRvHb1Mi73MuQdff9RQSvpaADWxShb4Z3lnJTtF65E63OUP8rPeEYwYA==
"@tryghost/kg-clean-basic-html@4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@tryghost/kg-clean-basic-html/-/kg-clean-basic-html-4.1.0.tgz#f4979d99e65a9202e3d3376f90ff08de8ac56578"
integrity sha512-exXUpTA1z0zyk3F3ZXcT7oy8b3acbpuHWMqiowdb2HI0/6LTGaBdmm866fhBKedgB59lIWRt511djUnUIILh+A==
"@tryghost/kg-converters@1.0.4":
version "1.0.4"
@ -7044,16 +7044,16 @@
lodash "^4.17.21"
luxon "^3.0.0"
"@tryghost/kg-default-nodes@1.0.16":
version "1.0.16"
resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.0.16.tgz#b9d3fafb549ee2f858d09a15b6b962a1b2e7a68d"
integrity sha512-obohpcnvNFXwFmtLViEHwcEA/tFZQm2CdzOT5vUaheq9LX/RXQN7/FVFQKhJ/Djv4ixHOufCp598X+Z3Ffnrqw==
"@tryghost/kg-default-nodes@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-1.1.0.tgz#e8902b5b8f7ac77614110c17fc0f0e1aa8daec13"
integrity sha512-Mx+c7q85SzDVZM/WFg+KKHOspOng0lG83i+YKaayAE0lU4yGzcFb1qV+TRIgn//4bMkMmjeKt8/0kp1GTfV0Ng==
dependencies:
"@lexical/clipboard" "0.13.1"
"@lexical/rich-text" "0.13.1"
"@lexical/selection" "0.13.1"
"@lexical/utils" "0.13.1"
"@tryghost/kg-clean-basic-html" "4.0.9"
"@tryghost/kg-clean-basic-html" "4.1.0"
"@tryghost/kg-markdown-html-renderer" "7.0.5"
html-minifier "^4.0.0"
jsdom "^24.0.0"
@ -7061,21 +7061,21 @@
lodash "^4.17.21"
luxon "^3.3.0"
"@tryghost/kg-default-transforms@1.0.17":
version "1.0.17"
resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.0.17.tgz#7383428b91b2cd389f655a876a5ed8e3c5c73679"
integrity sha512-l7Z6Hv81c+OTCtpzkMmdBoFZoF5rdc2Sqws635F6+GaTVzxPsxR9lf6lG7jnTmtDM/YKCgPFQiGu+CBOaDUNPw==
"@tryghost/kg-default-transforms@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.1.1.tgz#219e515738e7a6aa0c534091b30000e93000491c"
integrity sha512-r+++zB7XLAmkKPCMHHmrL0Qbg1NyRRHSaRslKYdC3Z5+Z16GTQfFzUAIAAY5A16Livj/hNbO8XlQrKNS3hV1tQ==
dependencies:
"@lexical/list" "0.13.1"
"@lexical/rich-text" "0.13.1"
"@lexical/utils" "0.13.1"
"@tryghost/kg-default-nodes" "1.0.16"
"@tryghost/kg-default-nodes" "1.1.0"
lexical "0.13.1"
"@tryghost/kg-html-to-lexical@1.0.17":
version "1.0.17"
resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.0.17.tgz#b89c68ba6120601f01454a439356c5232c1e4e2c"
integrity sha512-8SPXl02D0tmiUCo5tQ4IG511HixNlfdbeQCs7HAM+6gFUmkPCCByBtxeVQ2KkiUBcfe33/+D+JoGgZTvrBD16A==
"@tryghost/kg-html-to-lexical@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.1.1.tgz#412ef0be6feb97092bb22764e5578533085fd139"
integrity sha512-KXFJeRShAy4BoL+hIpUijXkPM0V8GxaHzcH81vuKgcSrGk3MG21tC3tD4YxZh5uc8jy9H7/oO9ZxzN0ATDMvVg==
dependencies:
"@lexical/clipboard" "0.13.1"
"@lexical/headless" "0.13.1"
@ -7083,15 +7083,15 @@
"@lexical/link" "0.13.1"
"@lexical/list" "0.13.1"
"@lexical/rich-text" "0.13.1"
"@tryghost/kg-default-nodes" "1.0.16"
"@tryghost/kg-default-transforms" "1.0.17"
"@tryghost/kg-default-nodes" "1.1.0"
"@tryghost/kg-default-transforms" "1.1.1"
jsdom "^24.0.0"
lexical "0.13.1"
"@tryghost/kg-lexical-html-renderer@1.0.16":
version "1.0.16"
resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.0.16.tgz#166697108b0000f6055f2f6e4e3619cbec633266"
integrity sha512-Wkk295SmA+vyj0CguJmCmMqZsLblUGBdEE+RsfJCmUr1VzwHXmORbO7wfKOTn4arBL2D37rk7rj+8K2OCuicog==
"@tryghost/kg-lexical-html-renderer@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.1.1.tgz#4a489cdc0268a15546be421e1b673eecb2616049"
integrity sha512-W41r93zfI17Ff0QxJOru2VxE6l5ICAwd2xVzX0OUOC6PxAJ2e5RrjXzzHjdEjB9eTYIteKH5m+488zMCR4SOVw==
dependencies:
"@lexical/clipboard" "0.13.1"
"@lexical/code" "0.13.1"
@ -7099,10 +7099,11 @@
"@lexical/link" "0.13.1"
"@lexical/list" "0.13.1"
"@lexical/rich-text" "0.13.1"
"@tryghost/kg-default-nodes" "1.0.16"
"@tryghost/kg-default-nodes" "1.1.0"
"@tryghost/kg-default-transforms" "1.1.1"
jsdom "^24.0.0"
lexical "0.13.1"
prettier "^3.0.0"
prettier "3.2.5"
"@tryghost/kg-markdown-html-renderer@7.0.5":
version "7.0.5"
@ -7128,17 +7129,17 @@
mobiledoc-dom-renderer "^0.7.0"
simple-dom "^1.4.0"
"@tryghost/kg-parser-plugins@4.0.9":
version "4.0.9"
resolved "https://registry.yarnpkg.com/@tryghost/kg-parser-plugins/-/kg-parser-plugins-4.0.9.tgz#66784a9df28b5cf105ceeff2fdf9245db09362b6"
integrity sha512-3GiP9HsJT4dSdg/3ww7cpkgdZ2MNxKkmpZPOPLwixQeUbWJaR1a+1a6X0C1Woc8djd/pHLaBVQWcTDtpB/NaUA==
"@tryghost/kg-parser-plugins@4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@tryghost/kg-parser-plugins/-/kg-parser-plugins-4.1.0.tgz#60f6492d1b0d71558c51f7b0c45d905ae5aa8b0a"
integrity sha512-sHwOc+9ObNBFccgbSxnbdgygLTPLI0PpjUwGsVJ7NAPQBEISHsQzHMYpWTCGatgdB+B+KmwTtBeGcvn3DIXagw==
dependencies:
"@tryghost/kg-clean-basic-html" "4.0.9"
"@tryghost/kg-clean-basic-html" "4.1.0"
"@tryghost/kg-unsplash-selector@0.1.17":
version "0.1.17"
resolved "https://registry.yarnpkg.com/@tryghost/kg-unsplash-selector/-/kg-unsplash-selector-0.1.17.tgz#6caa4ae0afbd3989981cbc17e7c909d243cadfc6"
integrity sha512-8cw4qtkczWBrAo7PpZ/kT78yzpm8B+cEwWW6McWkTzZRs3AuBUWWyCVi6plb1c+RhY110FoFkyMLBJnGeM+D3g==
"@tryghost/kg-unsplash-selector@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@tryghost/kg-unsplash-selector/-/kg-unsplash-selector-0.2.0.tgz#e811612fd66f20182a1d7f06e48a86bf94896ce6"
integrity sha512-oVMUiKFwOk5bUwNmW2DrICfs9lRtqHIOrV2hnJF8cBrhKLPnTSDrYTrrz04FTkq1ApISJ0y9pLZA6eepZpzTUQ==
"@tryghost/kg-utils@1.0.26":
version "1.0.26"
@ -7147,10 +7148,10 @@
dependencies:
semver "^7.3.5"
"@tryghost/koenig-lexical@1.1.16":
version "1.1.16"
resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.1.16.tgz#cdaed36dc97bde8f53a08880fa5cee171641fd93"
integrity sha512-u2dqnTA7AbA8GUPZVVVToSW91eqV0G42pbJeGTxmlzK91OIKa4fbrMO4Vp0HD3cMhCHYml9kWa976HowGS/NjQ==
"@tryghost/koenig-lexical@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.2.2.tgz#39cd50959de7b5ff8e0d2dbd8688ff83c81f57e1"
integrity sha512-1bFveIMMNg3Q6UPWGjTvg6m430N+4ZAMTWs2IfuR3iIVBPp687T41vgsiMQySfAepm/WKRAltpIh4kNYQOy1Ng==
"@tryghost/limit-service@1.2.14":
version "1.2.14"
@ -22801,10 +22802,10 @@ mysql2@3.9.7:
seq-queue "^0.0.5"
sqlstring "^2.3.2"
mysql2@3.9.8:
version "3.9.8"
resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.9.8.tgz#fe8a0f975f2c495ed76ca988ddc5505801dc49ce"
integrity sha512-+5JKNjPuks1FNMoy9TYpl77f+5frbTklz7eb3XDwbpsERRLEeXiW2PDEkakYF50UuKU2qwfGnyXpKYvukv8mGA==
mysql2@3.9.9:
version "3.9.9"
resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.9.9.tgz#262582b612a2dea540ef0d581b362d55958cf272"
integrity sha512-Qtb2RUxwWMFkWXqF7Rd/7ySkupbQnNY7O0zQuQYgPcuJZ06M36JG3HIDEh/pEeq7LImcvA6O3lOVQ9XQK+HEZg==
dependencies:
denque "^2.1.0"
generate-function "^2.3.1"
@ -25302,16 +25303,16 @@ pretender@3.4.7, pretender@^3.4.7:
fake-xml-http-request "^2.1.2"
route-recognizer "^0.3.3"
prettier@3.2.5:
version "3.2.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
prettier@^2.8.0:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prettier@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
pretty-format@^27.0.2:
version "27.5.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"