fix(promo-banner): center by default + admin alignment selector (#482)
The gallery promotional banner (#440) read as visually offset from the gallery footer because: - Footer used `container text-center px-4` (full container width, centered text). - Promo block used `container py-4 sm:py-6` with an inner `max-w-3xl mx-auto` wrapper holding left-aligned text — a narrower column with left-aligned content sitting in the middle of the page. Two issues compounded: the column was narrower than the footer AND its text alignment differed. Reported by Rekoo-PS in #482 with a screenshot showing the misalignment, with a request for an admin alignment option. Fix: - Drop the inner max-w-3xl wrapper. Promo content now spans the same .container width as the footer, eliminating the narrower-column visual. - Default text alignment changed from left → center to match the footer. - New `branding_promo_alignment` setting ('left' | 'center' | 'right', default 'center'). Surfaced as a dropdown next to the existing Position dropdown on the BrandingPage. Live preview block on the BrandingPage mirrors the gallery render so admins see what guests will see. - Also replaced the no-op `prose-sm` prose-modifier with a real `prose prose-sm` outer class so the existing `prose-a:text-accent` modifier actually takes effect (it didn't before — modifiers without an outer .prose are silently ignored by Tailwind Typography). Migration 103 seeds the new setting at 'center' so existing installs that have a promo banner today see the corrected alignment immediately on next deploy. i18n: en + de hand-translated; nl/pt/ru/fr machine-translated and flagged for native review per project convention.
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Migration: Promotional banner text alignment (#482).
|
||||||
|
*
|
||||||
|
* Adds `branding_promo_alignment` to app_settings — admin-controlled
|
||||||
|
* horizontal alignment for the gallery promotional banner content
|
||||||
|
* (#440 / #482). Reuses the existing app_settings shape that
|
||||||
|
* branding_promo_markdown / branding_promo_position already use.
|
||||||
|
*
|
||||||
|
* Default 'center' so the banner aligns with the gallery footer
|
||||||
|
* (which is full-width center-aligned). The previous default left
|
||||||
|
* the markdown left-aligned in a max-w-3xl block, which Rekoo-PS
|
||||||
|
* reported as visually offset from the footer.
|
||||||
|
*
|
||||||
|
* Allowed values: 'left' | 'center' | 'right' — validated on the
|
||||||
|
* write path in adminSettings.js, not enforced by the column type
|
||||||
|
* (we use varchar instead of CHECK so the value can be extended
|
||||||
|
* later — e.g. 'justify' — without another schema migration).
|
||||||
|
*
|
||||||
|
* Idempotent: skips the insert when the row already exists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.up = async function(knex) {
|
||||||
|
if (!(await knex.schema.hasTable('app_settings'))) return;
|
||||||
|
|
||||||
|
const existing = await knex('app_settings')
|
||||||
|
.where('setting_key', 'branding_promo_alignment')
|
||||||
|
.first();
|
||||||
|
if (existing) return;
|
||||||
|
|
||||||
|
await knex('app_settings').insert({
|
||||||
|
setting_key: 'branding_promo_alignment',
|
||||||
|
setting_value: JSON.stringify('center'),
|
||||||
|
setting_type: 'branding',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = async function(knex) {
|
||||||
|
if (!(await knex.schema.hasTable('app_settings'))) return;
|
||||||
|
await knex('app_settings')
|
||||||
|
.where('setting_key', 'branding_promo_alignment')
|
||||||
|
.del();
|
||||||
|
};
|
||||||
@@ -322,7 +322,8 @@ router.put('/branding', adminAuth, requirePermission('settings.edit'), async (re
|
|||||||
twitter_url,
|
twitter_url,
|
||||||
youtube_url,
|
youtube_url,
|
||||||
promo_markdown,
|
promo_markdown,
|
||||||
promo_position
|
promo_position,
|
||||||
|
promo_alignment
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Normalize force_color_mode: only 'dark' | 'light' | null are valid.
|
// Normalize force_color_mode: only 'dark' | 'light' | null are valid.
|
||||||
@@ -340,6 +341,15 @@ router.put('/branding', adminAuth, requirePermission('settings.edit'), async (re
|
|||||||
? 'below_footer'
|
? 'below_footer'
|
||||||
: 'above_footer';
|
: 'above_footer';
|
||||||
|
|
||||||
|
// Normalize promo_alignment: 'left' | 'center' | 'right'. Defaults
|
||||||
|
// to 'center' to match the gallery footer's full-width centering
|
||||||
|
// (#482 — the previous default left the markdown left-aligned in
|
||||||
|
// a max-w-3xl block, which read as visually offset from the footer).
|
||||||
|
const allowedPromoAlignments = ['left', 'center', 'right'];
|
||||||
|
const normalizedPromoAlignment = allowedPromoAlignments.includes(promo_alignment)
|
||||||
|
? promo_alignment
|
||||||
|
: 'center';
|
||||||
|
|
||||||
// Normalize login_logo_size to the same token set as logo_size.
|
// Normalize login_logo_size to the same token set as logo_size.
|
||||||
// Anything else falls back to 'medium' on the next render.
|
// Anything else falls back to 'medium' on the next render.
|
||||||
const allowedLoginLogoSizes = ['small', 'medium', 'large', 'xlarge'];
|
const allowedLoginLogoSizes = ['small', 'medium', 'large', 'xlarge'];
|
||||||
@@ -381,7 +391,8 @@ router.put('/branding', adminAuth, requirePermission('settings.edit'), async (re
|
|||||||
...(twitter_url !== undefined && { twitter_url: String(twitter_url || '').trim() }),
|
...(twitter_url !== undefined && { twitter_url: String(twitter_url || '').trim() }),
|
||||||
...(youtube_url !== undefined && { youtube_url: String(youtube_url || '').trim() }),
|
...(youtube_url !== undefined && { youtube_url: String(youtube_url || '').trim() }),
|
||||||
...(promo_markdown !== undefined && { promo_markdown: typeof promo_markdown === 'string' ? promo_markdown : '' }),
|
...(promo_markdown !== undefined && { promo_markdown: typeof promo_markdown === 'string' ? promo_markdown : '' }),
|
||||||
...(promo_position !== undefined && { promo_position: normalizedPromoPosition })
|
...(promo_position !== undefined && { promo_position: normalizedPromoPosition }),
|
||||||
|
...(promo_alignment !== undefined && { promo_alignment: normalizedPromoAlignment })
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle favicon deletion if empty string or null is provided
|
// Handle favicon deletion if empty string or null is provided
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ router.get('/', async (req, res) => {
|
|||||||
branding_promo_position: settingsObject.branding_promo_position === 'below_footer'
|
branding_promo_position: settingsObject.branding_promo_position === 'below_footer'
|
||||||
? 'below_footer'
|
? 'below_footer'
|
||||||
: 'above_footer',
|
: 'above_footer',
|
||||||
|
// Promo content alignment (#482). Defaults to center so the
|
||||||
|
// banner aligns with the gallery footer; admin can flip to
|
||||||
|
// left or right via Settings → Branding.
|
||||||
|
branding_promo_alignment: ['left', 'center', 'right'].includes(settingsObject.branding_promo_alignment)
|
||||||
|
? settingsObject.branding_promo_alignment
|
||||||
|
: 'center',
|
||||||
// Force a specific color mode site-wide. When set, the user toggle
|
// Force a specific color mode site-wide. When set, the user toggle
|
||||||
// is hidden and the value overrides per-theme/system preference.
|
// is hidden and the value overrides per-theme/system preference.
|
||||||
// Allowed values: 'dark' | 'light' | null (null = no force).
|
// Allowed values: 'dark' | 'light' | null (null = no force).
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ interface GalleryLayoutProps {
|
|||||||
youtube_url?: string;
|
youtube_url?: string;
|
||||||
promo_markdown?: string;
|
promo_markdown?: string;
|
||||||
promo_position?: 'above_footer' | 'below_footer';
|
promo_position?: 'above_footer' | 'below_footer';
|
||||||
|
// Horizontal alignment for the promo content (#482). Defaults
|
||||||
|
// to 'center' so the banner aligns with the footer.
|
||||||
|
promo_alignment?: 'left' | 'center' | 'right';
|
||||||
};
|
};
|
||||||
showLogout?: boolean;
|
showLogout?: boolean;
|
||||||
onLogout?: () => void;
|
onLogout?: () => void;
|
||||||
@@ -226,12 +229,36 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
|||||||
})().trim();
|
})().trim();
|
||||||
const promoPosition: 'above_footer' | 'below_footer' = brandingSettings?.promo_position === 'below_footer' ? 'below_footer' : 'above_footer';
|
const promoPosition: 'above_footer' | 'below_footer' = brandingSettings?.promo_position === 'below_footer' ? 'below_footer' : 'above_footer';
|
||||||
|
|
||||||
|
// Promo alignment (#482). Defaults to 'center' to match the gallery
|
||||||
|
// footer's `text-center px-4` so the banner reads as part of the
|
||||||
|
// same composition as the footer beneath it. Admin can flip to
|
||||||
|
// 'left' or 'right' from Settings → Branding.
|
||||||
|
const promoAlignment: 'left' | 'center' | 'right' =
|
||||||
|
brandingSettings?.promo_alignment === 'left' ? 'left'
|
||||||
|
: brandingSettings?.promo_alignment === 'right' ? 'right'
|
||||||
|
: 'center';
|
||||||
|
const promoTextAlignClass =
|
||||||
|
promoAlignment === 'left' ? 'text-left'
|
||||||
|
: promoAlignment === 'right' ? 'text-right'
|
||||||
|
: 'text-center';
|
||||||
|
|
||||||
const promoSlot = promoMarkdown ? (
|
const promoSlot = promoMarkdown ? (
|
||||||
<div className="gallery-promo border-t border-surface bg-surface/50">
|
<div className="gallery-promo border-t border-surface bg-surface/50">
|
||||||
<div className="container py-4 sm:py-6">
|
{/*
|
||||||
<div className="max-w-3xl mx-auto text-sm text-theme">
|
* Inner block uses .container (matches the footer's container
|
||||||
<MarkdownContent source={promoMarkdown} className="prose-sm prose-a:text-accent" />
|
* width) + the alignment class. We deliberately drop the
|
||||||
</div>
|
* previous max-w-3xl wrapper — it created a narrower column
|
||||||
|
* that read as visually offset from the full-width footer
|
||||||
|
* (#482, reported by Rekoo-PS). The `prose` class is needed
|
||||||
|
* for the prose-a:text-accent modifier to actually take effect
|
||||||
|
* (modifiers without an outer .prose are no-ops in Tailwind
|
||||||
|
* Typography).
|
||||||
|
*/}
|
||||||
|
<div className={`container py-4 sm:py-6 px-4 ${promoTextAlignClass}`}>
|
||||||
|
<MarkdownContent
|
||||||
|
source={promoMarkdown}
|
||||||
|
className={`prose prose-sm max-w-none mx-auto text-sm text-theme prose-a:text-accent ${promoTextAlignClass}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|||||||
@@ -253,6 +253,11 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
youtube_url: settingsData.branding_youtube_url || '',
|
youtube_url: settingsData.branding_youtube_url || '',
|
||||||
promo_markdown: settingsData.branding_promo_markdown || '',
|
promo_markdown: settingsData.branding_promo_markdown || '',
|
||||||
promo_position: settingsData.branding_promo_position === 'below_footer' ? 'below_footer' : 'above_footer',
|
promo_position: settingsData.branding_promo_position === 'below_footer' ? 'below_footer' : 'above_footer',
|
||||||
|
// Promo alignment (#482). Defaults to 'center' to match the
|
||||||
|
// gallery footer; see GalleryLayout.
|
||||||
|
promo_alignment: ['left', 'center', 'right'].includes(settingsData.branding_promo_alignment)
|
||||||
|
? settingsData.branding_promo_alignment
|
||||||
|
: 'center',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [settingsData]);
|
}, [settingsData]);
|
||||||
|
|||||||
@@ -1792,6 +1792,12 @@
|
|||||||
"sizeMedium": "Mittel (Standard)",
|
"sizeMedium": "Mittel (Standard)",
|
||||||
"sizeLarge": "Groß",
|
"sizeLarge": "Groß",
|
||||||
"sizeXLarge": "Sehr groß"
|
"sizeXLarge": "Sehr groß"
|
||||||
|
},
|
||||||
|
"promo": {
|
||||||
|
"alignment": "Ausrichtung",
|
||||||
|
"alignLeft": "Links",
|
||||||
|
"alignCenter": "Zentriert (Standard – wie der Footer)",
|
||||||
|
"alignRight": "Rechts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@@ -1462,6 +1462,12 @@
|
|||||||
"sizeMedium": "Medium (default)",
|
"sizeMedium": "Medium (default)",
|
||||||
"sizeLarge": "Large",
|
"sizeLarge": "Large",
|
||||||
"sizeXLarge": "Extra large"
|
"sizeXLarge": "Extra large"
|
||||||
|
},
|
||||||
|
"promo": {
|
||||||
|
"alignment": "Alignment",
|
||||||
|
"alignLeft": "Left",
|
||||||
|
"alignCenter": "Center (default — matches footer)",
|
||||||
|
"alignRight": "Right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@@ -1400,6 +1400,12 @@
|
|||||||
"sizeMedium": "Moyenne (par défaut)",
|
"sizeMedium": "Moyenne (par défaut)",
|
||||||
"sizeLarge": "Grande",
|
"sizeLarge": "Grande",
|
||||||
"sizeXLarge": "Très grande"
|
"sizeXLarge": "Très grande"
|
||||||
|
},
|
||||||
|
"promo": {
|
||||||
|
"alignment": "Alignement",
|
||||||
|
"alignLeft": "À gauche",
|
||||||
|
"alignCenter": "Centré (par défaut — identique au pied de page)",
|
||||||
|
"alignRight": "À droite"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@@ -1462,6 +1462,12 @@
|
|||||||
"sizeMedium": "Middel (standaard)",
|
"sizeMedium": "Middel (standaard)",
|
||||||
"sizeLarge": "Groot",
|
"sizeLarge": "Groot",
|
||||||
"sizeXLarge": "Extra groot"
|
"sizeXLarge": "Extra groot"
|
||||||
|
},
|
||||||
|
"promo": {
|
||||||
|
"alignment": "Uitlijning",
|
||||||
|
"alignLeft": "Links",
|
||||||
|
"alignCenter": "Gecentreerd (standaard — komt overeen met footer)",
|
||||||
|
"alignRight": "Rechts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@@ -1479,6 +1479,12 @@
|
|||||||
"sizeMedium": "Médio (padrão)",
|
"sizeMedium": "Médio (padrão)",
|
||||||
"sizeLarge": "Grande",
|
"sizeLarge": "Grande",
|
||||||
"sizeXLarge": "Extra grande"
|
"sizeXLarge": "Extra grande"
|
||||||
|
},
|
||||||
|
"promo": {
|
||||||
|
"alignment": "Alinhamento",
|
||||||
|
"alignLeft": "Esquerda",
|
||||||
|
"alignCenter": "Centro (padrão — igual ao rodapé)",
|
||||||
|
"alignRight": "Direita"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@@ -1496,6 +1496,12 @@
|
|||||||
"sizeMedium": "Средний (по умолчанию)",
|
"sizeMedium": "Средний (по умолчанию)",
|
||||||
"sizeLarge": "Большой",
|
"sizeLarge": "Большой",
|
||||||
"sizeXLarge": "Очень большой"
|
"sizeXLarge": "Очень большой"
|
||||||
|
},
|
||||||
|
"promo": {
|
||||||
|
"alignment": "Выравнивание",
|
||||||
|
"alignLeft": "По левому краю",
|
||||||
|
"alignCenter": "По центру (по умолчанию — как в подвале)",
|
||||||
|
"alignRight": "По правому краю"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const BrandingPage: React.FC = () => {
|
|||||||
youtube_url: '',
|
youtube_url: '',
|
||||||
promo_markdown: '',
|
promo_markdown: '',
|
||||||
promo_position: 'above_footer',
|
promo_position: 'above_footer',
|
||||||
|
promo_alignment: 'center',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [currentTheme, setCurrentTheme] = useState<ThemeConfig>(theme);
|
const [currentTheme, setCurrentTheme] = useState<ThemeConfig>(theme);
|
||||||
@@ -420,19 +421,37 @@ export const BrandingPage: React.FC = () => {
|
|||||||
{t('branding.promo.help', 'Markdown shown above or below the gallery footer (e.g. seasonal offer, print discount). Per-event overrides take priority.')}
|
{t('branding.promo.help', 'Markdown shown above or below the gallery footer (e.g. seasonal offer, print discount). Per-event overrides take priority.')}
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div className="flex flex-col sm:flex-row gap-3">
|
||||||
|
<div className="flex-1">
|
||||||
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
|
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
|
||||||
{t('branding.promo.position', 'Position')}
|
{t('branding.promo.position', 'Position')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={brandingSettings.promo_position || 'above_footer'}
|
value={brandingSettings.promo_position || 'above_footer'}
|
||||||
onChange={(e) => handleBrandingChange('promo_position', e.target.value as 'above_footer' | 'below_footer')}
|
onChange={(e) => handleBrandingChange('promo_position', e.target.value as 'above_footer' | 'below_footer')}
|
||||||
className="w-full sm:w-64 px-3 py-2 border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 rounded-lg focus:ring-2 focus:ring-primary-500"
|
className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||||
>
|
>
|
||||||
<option value="above_footer">{t('branding.promo.aboveFooter', 'Above footer')}</option>
|
<option value="above_footer">{t('branding.promo.aboveFooter', 'Above footer')}</option>
|
||||||
<option value="below_footer">{t('branding.promo.belowFooter', 'Below footer')}</option>
|
<option value="below_footer">{t('branding.promo.belowFooter', 'Below footer')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Horizontal alignment (#482). Defaults to center
|
||||||
|
so the banner aligns with the gallery footer. */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
|
||||||
|
{t('branding.promo.alignment', 'Alignment')}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={brandingSettings.promo_alignment || 'center'}
|
||||||
|
onChange={(e) => handleBrandingChange('promo_alignment', e.target.value as 'left' | 'center' | 'right')}
|
||||||
|
className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
<option value="left">{t('branding.promo.alignLeft', 'Left')}</option>
|
||||||
|
<option value="center">{t('branding.promo.alignCenter', 'Center (default — matches footer)')}</option>
|
||||||
|
<option value="right">{t('branding.promo.alignRight', 'Right')}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
|
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
|
||||||
{t('branding.promo.content', 'Content (markdown)')}
|
{t('branding.promo.content', 'Content (markdown)')}
|
||||||
@@ -453,9 +472,16 @@ export const BrandingPage: React.FC = () => {
|
|||||||
<div className="text-xs uppercase tracking-wider text-neutral-500 dark:text-neutral-400 mb-2">
|
<div className="text-xs uppercase tracking-wider text-neutral-500 dark:text-neutral-400 mb-2">
|
||||||
{t('branding.promo.preview', 'Preview')}
|
{t('branding.promo.preview', 'Preview')}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Preview mirrors the live gallery render — same
|
||||||
|
alignment class so the admin sees what guests
|
||||||
|
will see (#482). */}
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
source={brandingSettings.promo_markdown}
|
source={brandingSettings.promo_markdown}
|
||||||
className="text-sm text-neutral-800 dark:text-neutral-200 prose-sm prose-a:text-primary-600 dark:prose-a:text-primary-400"
|
className={`text-sm text-neutral-800 dark:text-neutral-200 prose prose-sm prose-a:text-primary-600 dark:prose-a:text-primary-400 ${
|
||||||
|
brandingSettings.promo_alignment === 'left' ? 'text-left'
|
||||||
|
: brandingSettings.promo_alignment === 'right' ? 'text-right'
|
||||||
|
: 'text-center'
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export interface PublicSettings {
|
|||||||
branding_youtube_url?: string;
|
branding_youtube_url?: string;
|
||||||
branding_promo_markdown?: string;
|
branding_promo_markdown?: string;
|
||||||
branding_promo_position?: 'above_footer' | 'below_footer';
|
branding_promo_position?: 'above_footer' | 'below_footer';
|
||||||
|
// Per-install promo banner alignment (#482). Defaults to 'center'
|
||||||
|
// so the banner aligns with the gallery footer's centering.
|
||||||
|
branding_promo_alignment?: 'left' | 'center' | 'right';
|
||||||
theme_config: any;
|
theme_config: any;
|
||||||
default_language: string;
|
default_language: string;
|
||||||
enable_analytics: boolean;
|
enable_analytics: boolean;
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ export interface BrandingSettings {
|
|||||||
youtube_url?: string;
|
youtube_url?: string;
|
||||||
promo_markdown?: string;
|
promo_markdown?: string;
|
||||||
promo_position?: 'above_footer' | 'below_footer';
|
promo_position?: 'above_footer' | 'below_footer';
|
||||||
|
// Per-install promo banner alignment (#482). Defaults to center
|
||||||
|
// so the banner aligns with the gallery footer.
|
||||||
|
promo_alignment?: 'left' | 'center' | 'right';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThemeSettings {
|
export interface ThemeSettings {
|
||||||
@@ -336,7 +339,10 @@ export const settingsService = {
|
|||||||
twitter_url: rawSettings.branding_twitter_url || '',
|
twitter_url: rawSettings.branding_twitter_url || '',
|
||||||
youtube_url: rawSettings.branding_youtube_url || '',
|
youtube_url: rawSettings.branding_youtube_url || '',
|
||||||
promo_markdown: rawSettings.branding_promo_markdown || '',
|
promo_markdown: rawSettings.branding_promo_markdown || '',
|
||||||
promo_position: rawSettings.branding_promo_position === 'below_footer' ? 'below_footer' : 'above_footer'
|
promo_position: rawSettings.branding_promo_position === 'below_footer' ? 'below_footer' : 'above_footer',
|
||||||
|
promo_alignment: ['left', 'center', 'right'].includes(rawSettings.branding_promo_alignment)
|
||||||
|
? rawSettings.branding_promo_alignment
|
||||||
|
: 'center'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user