-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: load favicon from WordPress server #119
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
67eb483
fix: interim solution to stop browser from making a request to /favic…
SH4LIN 9162141
refactor: separate icons metadata into individual function
SH4LIN 1c6514b
refactor: replace `Todo` with `@todo`
SH4LIN c8675c3
feature: load siteIcon from GeneralSettings in GraphQL, display two d…
SH4LIN d7e9af0
refactor: move icons fetching and it's utility functions into separat…
SH4LIN 8bb6a9b
fix: wrong variable usage in filter
SH4LIN 749de7b
refactor: move component expected types to the component file
SH4LIN f505dc1
refactor: parsed siteIcons data into a format that component requires
SH4LIN e80c63c
fix: fatal error with using push on undefined
SH4LIN f9d6dfe
Merge branch 'develop' into fix/favicon-loading
justlevine adf1723
chore: cleanup head
justlevine 09fcfa8
Merge branch 'develop' into fix/favicon-loading
justlevine 71c7c30
refactor: changed types to interface
SH4LIN 7e2e0fb
chore: add todos, and comments
SH4LIN 9d17f59
chore: update types make it compatible with exactOptionalPropertyTypes
SH4LIN 3f8cdf6
chore: add changeset
SH4LIN 6aaa00a
chore: rename fallBackIcons to fallbackIcons
SH4LIN 9b040a4
Merge branch 'develop' into fix/favicon-loading
justlevine 7fea521
chore: cleanup
justlevine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
"@snapwp/query": patch | ||
"@snapwp/next": patch | ||
"snapwp": patch | ||
--- | ||
|
||
feat: load default favicon metadata from WordPress |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,24 @@ | ||
import { RootLayout } from '@snapwp/next'; | ||
import { RootLayout, generateRootMetaData } from '@snapwp/next'; | ||
ayushnirwal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import type { Metadata } from 'next'; | ||
import type { PropsWithChildren } from 'react'; | ||
|
||
export default function Layout( { children }: { children: React.ReactNode } ) { | ||
export default function Layout( { children }: PropsWithChildren ) { | ||
return ( | ||
<RootLayout> | ||
<>{ children }</> | ||
</RootLayout> | ||
); | ||
} | ||
|
||
/** | ||
* Generate custom metadata with rootMetadata generated using generateRootMeraData. | ||
* | ||
* @return dynamic metadata generated from generateRootMetaData() and custom logic. | ||
*/ | ||
export async function generateMetadata(): Promise< Metadata > { | ||
const rootMetaData = await generateRootMetaData(); | ||
|
||
return { | ||
...rootMetaData, | ||
}; | ||
} |
justlevine marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import { QueryEngine } from '@snapwp/query'; | ||
|
||
interface GeneralSettingsProps { | ||
generalSettings: { | ||
siteIcon: { | ||
mediaItemUrl: string | undefined; | ||
mediaDetails: { | ||
sizes: IconData[]; | ||
}; | ||
}; | ||
}; | ||
} | ||
|
||
interface IconMetaData { | ||
faviconIcons: FormattedIconData[]; | ||
appleIcons: FormattedIconData[] | undefined; | ||
msApplicationTileIcon: IconData | undefined; | ||
} | ||
|
||
interface IconData { | ||
sourceUrl: string; | ||
height: string; | ||
width: string; | ||
} | ||
|
||
interface FormattedIconData { | ||
url: string; | ||
sizes: string; | ||
} | ||
|
||
/** | ||
* Fetch site icon data and filter them into categorized formats. | ||
* @see - https://developer.wordpress.org/reference/functions/wp_site_icon/#:~:text=Displays%20site%20icon%20meta%20tags. Reason why we are different resolution icons into individual category. | ||
ayushnirwal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* @todo Refactor for composability alongside SEO metadata patterns | ||
* | ||
* @return Categorized icons. | ||
*/ | ||
export const getIcons = async (): Promise< IconMetaData > => { | ||
const settings: GeneralSettingsProps | undefined = | ||
await QueryEngine.getGeneralSettings(); | ||
|
||
if ( ! settings ) { | ||
return { | ||
faviconIcons: [], | ||
appleIcons: undefined, | ||
msApplicationTileIcon: undefined, | ||
}; | ||
} | ||
|
||
let fallbackIcons: IconMetaData = { | ||
faviconIcons: [], | ||
appleIcons: undefined, | ||
msApplicationTileIcon: undefined, | ||
}; | ||
|
||
// Creating fallback icons if siteIcon is present but mediaDetails is not. | ||
if ( settings.generalSettings.siteIcon.mediaItemUrl ) { | ||
fallbackIcons = { | ||
...fallbackIcons, | ||
faviconIcons: [ | ||
{ | ||
sizes: '512x512', | ||
url: settings.generalSettings.siteIcon.mediaItemUrl, | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
// Return fallback icons if sizes are not present. | ||
if ( ! settings.generalSettings.siteIcon.mediaDetails.sizes ) { | ||
return fallbackIcons; | ||
} | ||
|
||
const sizes = settings.generalSettings.siteIcon.mediaDetails.sizes; | ||
|
||
// Filter out valid icons | ||
const validIcons: IconData[] = sizes.filter( | ||
( icon ) => !! ( icon.sourceUrl && icon.height && icon.width ) | ||
) as IconData[]; | ||
|
||
// Return fallback icons if no valid icons are found. | ||
if ( ! validIcons.length ) { | ||
return fallbackIcons; | ||
} | ||
|
||
// Filter icons by sizes. | ||
const filteredFaviconIcons = filterIconsBySize( validIcons, [ | ||
'32x32', | ||
'192x192', | ||
] ); | ||
|
||
// Format icons into required metadata structure. If filteredFaviconIcons is empty, return # as url of icon. | ||
const formattedFaviconIcons: FormattedIconData[] = filteredFaviconIcons | ||
? formatIcons( filteredFaviconIcons ) | ||
: [ { url: '#', sizes: '' } ]; | ||
|
||
const filteredAppleIcons = filterIconsBySize( validIcons, [ '180x180' ] ); | ||
|
||
// Format icons into required metadata structure. | ||
const formattedAppleIcons: FormattedIconData[] = filteredAppleIcons | ||
? formatIcons( filteredAppleIcons ) | ||
: []; | ||
|
||
return { | ||
faviconIcons: formattedFaviconIcons, | ||
appleIcons: formattedAppleIcons, | ||
msApplicationTileIcon: findIconBySize( validIcons, '270x270' ), | ||
}; | ||
}; | ||
|
||
/** | ||
* Filter icons by multiple size formats. | ||
* | ||
* @param icons - List of all available icons. | ||
* @param sizes - Array of sizes in "WxH" format (e.g., "32x32"). | ||
* @return Filtered list of icons. | ||
*/ | ||
export const filterIconsBySize = ( | ||
icons: IconData[], | ||
sizes: string[] | ||
): IconData[] => | ||
icons.filter( ( icon ) => | ||
sizes.includes( `${ icon.width }x${ icon.height }` ) | ||
); | ||
|
||
/** | ||
* Find a single icon matching the specified size. | ||
* | ||
* @param icons - List of icons. | ||
* @param size - Size in "WxH" format (e.g., "270x270"). | ||
* @return The first matching icon or undefined. | ||
*/ | ||
export const findIconBySize = ( | ||
icons: IconData[], | ||
size: string | ||
): IconData | undefined => | ||
icons.find( ( icon ) => `${ icon.width }x${ icon.height }` === size ); | ||
|
||
/** | ||
* Format icons into the required metadata structure. | ||
* | ||
* @param icons - List of icons. | ||
* | ||
* @return Formatted list of icons. | ||
*/ | ||
const formatIcons = ( icons: IconData[] ): FormattedIconData[] => | ||
icons.map( ( { sourceUrl, width, height } ) => ( { | ||
url: sourceUrl, | ||
sizes: `${ width }x${ height }`, | ||
} ) ); |
justlevine marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
query GetGeneralSettings { | ||
generalSettings { | ||
siteIcon { | ||
justlevine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
id | ||
mediaDetails { | ||
sizes { | ||
width | ||
height | ||
sourceUrl | ||
} | ||
} | ||
mediaItemUrl | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { Logger } from '@snapwp/core'; | ||
import type { GetGeneralSettingsQuery } from '@graphqlTypes/graphql'; | ||
import type { ApolloQueryResult } from '@apollo/client'; | ||
|
||
/** | ||
* @param queryData - The data fetched from the general settings query. | ||
* | ||
* @throws Throws an error if the query data is missing or invalid. | ||
* | ||
* @return An object containing parsed general settings data. | ||
*/ | ||
export default function parseGeneralSettings( | ||
queryData: ApolloQueryResult< GetGeneralSettingsQuery > | ||
) { | ||
if ( queryData.errors?.length ) { | ||
queryData.errors?.forEach( ( error ) => { | ||
Logger.error( `Error fetching global styles: ${ error }` ); | ||
} ); | ||
} | ||
|
||
// If queryData does not have generalSettings or siteIcon, return undefined because without siteIcon, generalSettings is not valid. | ||
if ( ! queryData.data.generalSettings?.siteIcon ) { | ||
return undefined; | ||
} | ||
|
||
const sizes: | ||
| undefined | ||
| { sourceUrl: string; height: string; width: string }[] = []; | ||
|
||
// If mediaDetails and sizes are present, parse the sizes. | ||
if ( queryData.data.generalSettings.siteIcon.mediaDetails?.sizes ) { | ||
queryData.data.generalSettings.siteIcon.mediaDetails.sizes.forEach( | ||
( size ) => { | ||
// If size is null or undefined skip the iteration. | ||
if ( ! size ) { | ||
return; | ||
} | ||
|
||
// If sourceUrl, height and width are not present, skip the iteration. | ||
if ( ! ( size.sourceUrl && size.height && size.width ) ) { | ||
return; | ||
} | ||
|
||
sizes.push( { | ||
sourceUrl: size.sourceUrl, | ||
height: size.height, | ||
width: size.width, | ||
} ); | ||
} | ||
); | ||
} | ||
|
||
// MediaItemUrl and mediaDetails are optional fields. Meaning if either of them is there, it should be returned. | ||
return { | ||
generalSettings: { | ||
siteIcon: { | ||
// If mediaItemUrl is not present, return undefined because it is an optional field. | ||
mediaItemUrl: | ||
queryData.data.generalSettings.siteIcon.mediaItemUrl || | ||
undefined, | ||
mediaDetails: { | ||
sizes, | ||
}, | ||
}, | ||
}, | ||
}; | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder, changesets is "dumb" and
major|minor|patch
is mapped blindly tox.y.z
, not real semver