Add next/script customization
This commit is contained in:
parent
8482a76dd6
commit
47fe1cf383
@ -194,6 +194,12 @@ Application behavior can be changed by configuring the following environment var
|
|||||||
- `NEXT_PUBLIC_SITE_FEEDS = 1` enables feeds at `/feed.json` and `/rss.xml`
|
- `NEXT_PUBLIC_SITE_FEEDS = 1` enables feeds at `/feed.json` and `/rss.xml`
|
||||||
- `NEXT_PUBLIC_OG_TEXT_ALIGNMENT = BOTTOM` keeps OG image text bottom aligned (default is top)
|
- `NEXT_PUBLIC_OG_TEXT_ALIGNMENT = BOTTOM` keeps OG image text bottom aligned (default is top)
|
||||||
|
|
||||||
|
#### Scripts & Analytics
|
||||||
|
- `PAGE_SCRIPT_URLS`
|
||||||
|
- comma-separated list of URLs to be added to the bottom of the body tag via "next/script"
|
||||||
|
- urls must begin with 'https'
|
||||||
|
- ⚠️ this will invoke arbitrary script execution on every page—use with caution
|
||||||
|
|
||||||
## Alternate storage providers
|
## Alternate storage providers
|
||||||
|
|
||||||
Only one storage adapter—Vercel Blob, Cloudflare R2, AWS S3, or MinIO—can be used at a time. Ideally, this is configured before photos are uploaded (see [Issue #34](https://github.com/sambecker/exif-photo-blog/issues/34) for migration considerations). If you have multiple adapters, you can set one as preferred by storing `aws-s3`, `cloudflare-r2`, `minio`, or `vercel-blob` in `NEXT_PUBLIC_STORAGE_PREFERENCE`. See [FAQ](#will-there-be-support-for-image-storage-providers-beyond-vercel-aws-and-cloudflare) regarding unsupported providers.
|
Only one storage adapter—Vercel Blob, Cloudflare R2, AWS S3, or MinIO—can be used at a time. Ideally, this is configured before photos are uploaded (see [Issue #34](https://github.com/sambecker/exif-photo-blog/issues/34) for migration considerations). If you have multiple adapters, you can set one as preferred by storing `aws-s3`, `cloudflare-r2`, `minio`, or `vercel-blob` in `NEXT_PUBLIC_STORAGE_PREFERENCE`. See [FAQ](#will-there-be-support-for-image-storage-providers-beyond-vercel-aws-and-cloudflare) regarding unsupported providers.
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
HTML_LANG,
|
HTML_LANG,
|
||||||
SITE_FEEDS_ENABLED,
|
SITE_FEEDS_ENABLED,
|
||||||
ADMIN_DEBUG_TOOLS_ENABLED,
|
ADMIN_DEBUG_TOOLS_ENABLED,
|
||||||
|
PAGE_SCRIPT_URLS,
|
||||||
} from '@/app/config';
|
} from '@/app/config';
|
||||||
import AppStateProvider from '@/app/AppStateProvider';
|
import AppStateProvider from '@/app/AppStateProvider';
|
||||||
import ToasterWithThemes from '@/toast/ToasterWithThemes';
|
import ToasterWithThemes from '@/toast/ToasterWithThemes';
|
||||||
@ -30,6 +31,7 @@ import SharedHoverProvider from '@/components/shared-hover/SharedHoverProvider';
|
|||||||
import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/path';
|
import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/path';
|
||||||
import SelectPhotosProvider from '@/admin/select/SelectPhotosProvider';
|
import SelectPhotosProvider from '@/admin/select/SelectPhotosProvider';
|
||||||
import AdminBatchEditPanel from '@/admin/select/AdminBatchEditPanel';
|
import AdminBatchEditPanel from '@/admin/select/AdminBatchEditPanel';
|
||||||
|
import Script from 'next/script';
|
||||||
|
|
||||||
import '../tailwind.css';
|
import '../tailwind.css';
|
||||||
|
|
||||||
@ -137,13 +139,14 @@ export default function RootLayout({
|
|||||||
</SharedHoverProvider>
|
</SharedHoverProvider>
|
||||||
</SwrConfigClient>
|
</SwrConfigClient>
|
||||||
<Analytics debug={false} />
|
<Analytics debug={false} />
|
||||||
<SpeedInsights debug={false} />
|
<SpeedInsights debug={false} />
|
||||||
<PhotoEscapeHandler />
|
<PhotoEscapeHandler />
|
||||||
<ToasterWithThemes />
|
<ToasterWithThemes />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SelectPhotosProvider>
|
</SelectPhotosProvider>
|
||||||
</AppTextProvider>
|
</AppTextProvider>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
|
{PAGE_SCRIPT_URLS.map(url => <Script key={url} src={url} />)}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -34,6 +34,8 @@ import ColorDot from '@/photo/color/ColorDot';
|
|||||||
import { Oklch } from '@/photo/color/client';
|
import { Oklch } from '@/photo/color/client';
|
||||||
import { getOrderedKeyListStatus } from '@/utility/key';
|
import { getOrderedKeyListStatus } from '@/utility/key';
|
||||||
import { DEFAULT_SOCIAL_KEYS, SOCIAL_KEYS } from '@/social';
|
import { DEFAULT_SOCIAL_KEYS, SOCIAL_KEYS } from '@/social';
|
||||||
|
import MaskedScroll from '@/components/MaskedScroll';
|
||||||
|
import { IoLink } from 'react-icons/io5';
|
||||||
|
|
||||||
export default function AdminAppConfigurationClient({
|
export default function AdminAppConfigurationClient({
|
||||||
// Storage
|
// Storage
|
||||||
@ -122,6 +124,9 @@ export default function AdminAppConfigurationClient({
|
|||||||
socialKeys,
|
socialKeys,
|
||||||
areSiteFeedsEnabled,
|
areSiteFeedsEnabled,
|
||||||
isOgTextBottomAligned,
|
isOgTextBottomAligned,
|
||||||
|
// Scripts & Analytics
|
||||||
|
hasPageScriptUrls,
|
||||||
|
pageScriptUrls,
|
||||||
// Internal
|
// Internal
|
||||||
areInternalToolsEnabled,
|
areInternalToolsEnabled,
|
||||||
areAdminDebugToolsEnabled,
|
areAdminDebugToolsEnabled,
|
||||||
@ -885,6 +890,35 @@ export default function AdminAppConfigurationClient({
|
|||||||
{renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])}
|
{renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])}
|
||||||
</ChecklistRow>
|
</ChecklistRow>
|
||||||
</>;
|
</>;
|
||||||
|
case 'Scripts & Analytics':
|
||||||
|
return <>
|
||||||
|
<ChecklistRow
|
||||||
|
title="Custom page scripts"
|
||||||
|
status={hasPageScriptUrls}
|
||||||
|
optional
|
||||||
|
>
|
||||||
|
{pageScriptUrls.length > 0 &&
|
||||||
|
<div className="mt-2 text-xs space-y-1.5">
|
||||||
|
{pageScriptUrls.map(url =>
|
||||||
|
<MaskedScroll
|
||||||
|
key={url}
|
||||||
|
className={clsx(
|
||||||
|
'inline-flex items-center gap-1',
|
||||||
|
'bg-dim rounded-md px-1.5 py-0.5',
|
||||||
|
)}
|
||||||
|
direction="horizontal"
|
||||||
|
>
|
||||||
|
<IoLink size={14} className="shrink-0 translate-y-[0.5px]"/>
|
||||||
|
<span className="font-medium text-nowrap">
|
||||||
|
{url}
|
||||||
|
</span>
|
||||||
|
</MaskedScroll>)}
|
||||||
|
</div>}
|
||||||
|
Set environment variable to comma-separated list of URLs
|
||||||
|
to be added to the bottom of the body tag via {'"next/script"'}:
|
||||||
|
{renderEnvVars(['PAGE_SCRIPT_URLS'])}
|
||||||
|
</ChecklistRow>
|
||||||
|
</>;
|
||||||
case 'Internal':
|
case 'Internal':
|
||||||
return <>
|
return <>
|
||||||
<ChecklistRow
|
<ChecklistRow
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { HiOutlineCog, HiSparkles } from 'react-icons/hi';
|
|||||||
import { IoMdGrid } from 'react-icons/io';
|
import { IoMdGrid } from 'react-icons/io';
|
||||||
import { PiPaintBrushHousehold } from 'react-icons/pi';
|
import { PiPaintBrushHousehold } from 'react-icons/pi';
|
||||||
import { RiSpeedMiniLine } from 'react-icons/ri';
|
import { RiSpeedMiniLine } from 'react-icons/ri';
|
||||||
|
import { TbBrandGoogleAnalytics } from 'react-icons/tb';
|
||||||
|
|
||||||
export interface AdminConfigSection {
|
export interface AdminConfigSection {
|
||||||
title: string;
|
title: string;
|
||||||
@ -59,6 +60,10 @@ const ADMIN_CONFIG_SECTIONS = [{
|
|||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
required: false,
|
required: false,
|
||||||
icon: <HiOutlineCog size={17} className="translate-y-[0.5px]" />,
|
icon: <HiOutlineCog size={17} className="translate-y-[0.5px]" />,
|
||||||
|
}, {
|
||||||
|
title: 'Scripts & Analytics',
|
||||||
|
required: false,
|
||||||
|
icon: <TbBrandGoogleAnalytics size={18} className="translate-y-[1px]" />,
|
||||||
}, {
|
}, {
|
||||||
title: 'Internal',
|
title: 'Internal',
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@ -370,6 +370,15 @@ export const SITE_FEEDS_ENABLED =
|
|||||||
export const OG_TEXT_BOTTOM_ALIGNMENT =
|
export const OG_TEXT_BOTTOM_ALIGNMENT =
|
||||||
(process.env.NEXT_PUBLIC_OG_TEXT_ALIGNMENT ?? '').toUpperCase() === 'BOTTOM';
|
(process.env.NEXT_PUBLIC_OG_TEXT_ALIGNMENT ?? '').toUpperCase() === 'BOTTOM';
|
||||||
|
|
||||||
|
// SCRIPTS & ANALYTICS
|
||||||
|
|
||||||
|
export const PAGE_SCRIPT_URLS = process.env.PAGE_SCRIPT_URLS
|
||||||
|
? process.env.PAGE_SCRIPT_URLS
|
||||||
|
.split(',')
|
||||||
|
.map(url => url.trim().toLocaleLowerCase())
|
||||||
|
.filter(url => url.startsWith('https://'))
|
||||||
|
: [];
|
||||||
|
|
||||||
// INTERNAL
|
// INTERNAL
|
||||||
|
|
||||||
export const ADMIN_DEBUG_TOOLS_ENABLED = process.env.ADMIN_DEBUG_TOOLS === '1';
|
export const ADMIN_DEBUG_TOOLS_ENABLED = process.env.ADMIN_DEBUG_TOOLS === '1';
|
||||||
@ -489,6 +498,9 @@ export const APP_CONFIGURATION = {
|
|||||||
socialKeys: SOCIAL_NETWORKS,
|
socialKeys: SOCIAL_NETWORKS,
|
||||||
areSiteFeedsEnabled: SITE_FEEDS_ENABLED,
|
areSiteFeedsEnabled: SITE_FEEDS_ENABLED,
|
||||||
isOgTextBottomAligned: OG_TEXT_BOTTOM_ALIGNMENT,
|
isOgTextBottomAligned: OG_TEXT_BOTTOM_ALIGNMENT,
|
||||||
|
// Scripts & Analytics
|
||||||
|
hasPageScriptUrls: PAGE_SCRIPT_URLS.length > 0,
|
||||||
|
pageScriptUrls: PAGE_SCRIPT_URLS,
|
||||||
// Internal
|
// Internal
|
||||||
areInternalToolsEnabled: (
|
areInternalToolsEnabled: (
|
||||||
ADMIN_DEBUG_TOOLS_ENABLED ||
|
ADMIN_DEBUG_TOOLS_ENABLED ||
|
||||||
|
|||||||
@ -84,7 +84,7 @@ export default function useMaskedScroll({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ref = containerRef?.current;
|
const ref = containerRef?.current;
|
||||||
const contentRect = ref?.children[0].getBoundingClientRect();
|
const contentRect = ref?.children[0]?.getBoundingClientRect();
|
||||||
if (scrollToEndOnMount && ref && contentRect) {
|
if (scrollToEndOnMount && ref && contentRect) {
|
||||||
ref.scrollTo(isVertical
|
ref.scrollTo(isVertical
|
||||||
? { top: contentRect.height }
|
? { top: contentRect.height }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user