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_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
|
||||
|
||||
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,
|
||||
SITE_FEEDS_ENABLED,
|
||||
ADMIN_DEBUG_TOOLS_ENABLED,
|
||||
PAGE_SCRIPT_URLS,
|
||||
} from '@/app/config';
|
||||
import AppStateProvider from '@/app/AppStateProvider';
|
||||
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 SelectPhotosProvider from '@/admin/select/SelectPhotosProvider';
|
||||
import AdminBatchEditPanel from '@/admin/select/AdminBatchEditPanel';
|
||||
import Script from 'next/script';
|
||||
|
||||
import '../tailwind.css';
|
||||
|
||||
@ -144,6 +146,7 @@ export default function RootLayout({
|
||||
</SelectPhotosProvider>
|
||||
</AppTextProvider>
|
||||
</AppStateProvider>
|
||||
{PAGE_SCRIPT_URLS.map(url => <Script key={url} src={url} />)}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@ -34,6 +34,8 @@ import ColorDot from '@/photo/color/ColorDot';
|
||||
import { Oklch } from '@/photo/color/client';
|
||||
import { getOrderedKeyListStatus } from '@/utility/key';
|
||||
import { DEFAULT_SOCIAL_KEYS, SOCIAL_KEYS } from '@/social';
|
||||
import MaskedScroll from '@/components/MaskedScroll';
|
||||
import { IoLink } from 'react-icons/io5';
|
||||
|
||||
export default function AdminAppConfigurationClient({
|
||||
// Storage
|
||||
@ -122,6 +124,9 @@ export default function AdminAppConfigurationClient({
|
||||
socialKeys,
|
||||
areSiteFeedsEnabled,
|
||||
isOgTextBottomAligned,
|
||||
// Scripts & Analytics
|
||||
hasPageScriptUrls,
|
||||
pageScriptUrls,
|
||||
// Internal
|
||||
areInternalToolsEnabled,
|
||||
areAdminDebugToolsEnabled,
|
||||
@ -885,6 +890,35 @@ export default function AdminAppConfigurationClient({
|
||||
{renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])}
|
||||
</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':
|
||||
return <>
|
||||
<ChecklistRow
|
||||
|
||||
@ -6,6 +6,7 @@ import { HiOutlineCog, HiSparkles } from 'react-icons/hi';
|
||||
import { IoMdGrid } from 'react-icons/io';
|
||||
import { PiPaintBrushHousehold } from 'react-icons/pi';
|
||||
import { RiSpeedMiniLine } from 'react-icons/ri';
|
||||
import { TbBrandGoogleAnalytics } from 'react-icons/tb';
|
||||
|
||||
export interface AdminConfigSection {
|
||||
title: string;
|
||||
@ -59,6 +60,10 @@ const ADMIN_CONFIG_SECTIONS = [{
|
||||
title: 'Settings',
|
||||
required: false,
|
||||
icon: <HiOutlineCog size={17} className="translate-y-[0.5px]" />,
|
||||
}, {
|
||||
title: 'Scripts & Analytics',
|
||||
required: false,
|
||||
icon: <TbBrandGoogleAnalytics size={18} className="translate-y-[1px]" />,
|
||||
}, {
|
||||
title: 'Internal',
|
||||
required: false,
|
||||
|
||||
@ -370,6 +370,15 @@ export const SITE_FEEDS_ENABLED =
|
||||
export const OG_TEXT_BOTTOM_ALIGNMENT =
|
||||
(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
|
||||
|
||||
export const ADMIN_DEBUG_TOOLS_ENABLED = process.env.ADMIN_DEBUG_TOOLS === '1';
|
||||
@ -489,6 +498,9 @@ export const APP_CONFIGURATION = {
|
||||
socialKeys: SOCIAL_NETWORKS,
|
||||
areSiteFeedsEnabled: SITE_FEEDS_ENABLED,
|
||||
isOgTextBottomAligned: OG_TEXT_BOTTOM_ALIGNMENT,
|
||||
// Scripts & Analytics
|
||||
hasPageScriptUrls: PAGE_SCRIPT_URLS.length > 0,
|
||||
pageScriptUrls: PAGE_SCRIPT_URLS,
|
||||
// Internal
|
||||
areInternalToolsEnabled: (
|
||||
ADMIN_DEBUG_TOOLS_ENABLED ||
|
||||
|
||||
@ -84,7 +84,7 @@ export default function useMaskedScroll({
|
||||
|
||||
useEffect(() => {
|
||||
const ref = containerRef?.current;
|
||||
const contentRect = ref?.children[0].getBoundingClientRect();
|
||||
const contentRect = ref?.children[0]?.getBoundingClientRect();
|
||||
if (scrollToEndOnMount && ref && contentRect) {
|
||||
ref.scrollTo(isVertical
|
||||
? { top: contentRect.height }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user