Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
- Loading branch information
1 parent
ea66558
commit b94de30
Showing
32 changed files
with
1,034 additions
and
147 deletions.
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
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 |
---|---|---|
|
@@ -11,3 +11,7 @@ html.dark { | |
background-color: #151515; | ||
color: white; | ||
} | ||
|
||
::selection { | ||
background: #8884; | ||
} |
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
24 changes: 24 additions & 0 deletions
24
packages/devtools-ui-kit/src/components/NTextExternalLink.vue
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,24 @@ | ||
<script setup lang="ts"> | ||
import NLink from './NLink.vue' | ||
defineProps<{ | ||
link?: string | ||
}>() | ||
</script> | ||
|
||
<template> | ||
<component | ||
:is="link ? NLink : 'div'" | ||
v-bind="link ? { | ||
href: link, | ||
target: '_blank', | ||
rel: 'noopener noreferrer', | ||
} : {}" | ||
> | ||
<slot /> | ||
<div | ||
v-if="link" | ||
i-carbon:arrow-up-right translate-y--1 text-xs op50 | ||
/> | ||
</component> | ||
</template> |
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
132 changes: 132 additions & 0 deletions
132
packages/devtools/client/components/OpenGraphMissingTabs.vue
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,132 @@ | ||
<script setup lang="ts"> | ||
import { defu } from 'defu' | ||
import type { ReactiveHead } from '@unhead/vue' | ||
import type { NormalizedHeadTag } from '../../src/types' | ||
import { ogTags } from '../data/open-graph' | ||
const props = defineProps<{ | ||
tags: NormalizedHeadTag[] | ||
matchedRouteFilepath?: string | ||
}>() | ||
const missingTags = computed(() => { | ||
return ogTags.filter(define => !props.tags?.some(tag => tag.name === define.name)) | ||
}) | ||
const missingRequiredTags = computed(() => { | ||
return missingTags.value.filter(i => i.suggestion === 'required') | ||
}) | ||
const missingRecommendedTags = computed(() => { | ||
return missingTags.value.filter(i => i.suggestion === 'recommended') | ||
}) | ||
const mergedMissingTags = computed(() => { | ||
let data: Partial<ReactiveHead> = {} | ||
missingTags.value | ||
.forEach((tag) => { | ||
data = defu(data, tag.default) | ||
}) | ||
return data | ||
}) | ||
const codeSnippet = computed(() => { | ||
const body = JSON.stringify(mergedMissingTags.value, null, 2) | ||
.replace(/"([^"]+)":/g, '$1:') | ||
.replace(/"/g, '\'') | ||
return `useHead(${body})` | ||
}) | ||
const copy = useCopy() | ||
const openInEditor = useOpenInEditor() | ||
const tabs = [ | ||
'Missing Tags', | ||
'Code Snippet', | ||
] | ||
const selectedTab = ref(tabs[0]) | ||
</script> | ||
|
||
<template> | ||
<template v-if="missingTags.length"> | ||
<NSectionBlock | ||
text="Missing Tags" | ||
:description="`${missingTags.length} missing tags (${missingRequiredTags.length} required, ${missingRecommendedTags.length} recommended)`" | ||
icon="carbon:warning-other" | ||
header-class="text-orange op100! [[open]_&]:text-inherit" | ||
:padding="false" | ||
> | ||
<div flex="~ wrap" mt--2 w-full flex-none> | ||
<template v-for="name, idx of tabs" :key="idx"> | ||
<button | ||
px4 py2 border="r t base" | ||
hover="bg-active" | ||
:class="name === selectedTab ? '' : 'border-b'" | ||
@click="selectedTab = name" | ||
> | ||
<div :class="name === selectedTab ? '' : 'op30' " capitalize> | ||
{{ name }} | ||
</div> | ||
</button> | ||
</template> | ||
<div border="b base" flex-auto /> | ||
</div> | ||
|
||
<NCard v-if="selectedTab === tabs[0]" grid="~ cols-[max-content_1fr]" m4 items-center justify-between of-hidden> | ||
<template v-for="item, index of missingTags" :key="index"> | ||
<div v-if="index" x-divider /> | ||
<div v-if="index" x-divider /> | ||
<div flex="~ gap-1 items-center" px4 py2> | ||
<div i-carbon-warning text-orange /> | ||
<NTextExternalLink | ||
op50 | ||
:link="item.docs" | ||
n="orange" | ||
> | ||
{{ item.name }} | ||
</NTextExternalLink> | ||
</div> | ||
<!-- TODO: use icons instead, show link to documentation --> | ||
<div w-full p2 op75> | ||
{{ item.description }} | ||
</div> | ||
</template> | ||
</NCard> | ||
<div v-else m4 flex="~ col gap-2"> | ||
<p flex="~ gap-1 wrap items-center"> | ||
<NButton | ||
icon="carbon-copy" n="xs" px-2 | ||
@click="copy(codeSnippet)" | ||
> | ||
Copy | ||
</NButton> | ||
the following code snippet and paste it into your | ||
<NButton | ||
v-if="matchedRouteFilepath" | ||
icon="carbon-launch" n="xs" px-2 | ||
@click="openInEditor(matchedRouteFilepath)" | ||
> | ||
page component | ||
</NButton> | ||
<span v-else>page component</span> | ||
to full fill the missing tags. | ||
</p> | ||
<NCard relative n-code-block> | ||
<NCodeBlock | ||
:code="codeSnippet" | ||
lang="ts" | ||
:lines="false" | ||
w-full of-auto p3 | ||
/> | ||
<div flex="~ gap-2" n="sm primary" absolute right-2 top-2> | ||
<NButton | ||
icon="carbon-copy" | ||
@click="copy(codeSnippet)" | ||
> | ||
Copy | ||
</NButton> | ||
</div> | ||
</NCard> | ||
</div> | ||
</NSectionBlock> | ||
</template> | ||
</template> |
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,14 @@ | ||
# Open Graph | ||
|
||
Nuxt provides several different ways to manage your meta tags using [`unhead`](https://unhead.harlanzw.com/). Improve your Nuxt app's SEO with powerful head config, composables and components. | ||
|
||
[Learn more on the documentation](https://nuxt.com/docs/getting-started/seo-meta) | ||
|
||
--- | ||
|
||
You can also find how open graph specs are defined in: | ||
|
||
- [The Open Graph protocol](https://ogp.me/) | ||
- [Twitter Cards](https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started) | ||
|
||
<!-- and maybe also add reference to SEO modules(https://nuxt.com/modules?category=SEO) ? --> |
30 changes: 30 additions & 0 deletions
30
packages/devtools/client/components/social/SocialFacebook.vue
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,30 @@ | ||
<script setup lang="ts"> | ||
import type { SocialPreviewResolved } from '~/../src/types' | ||
defineProps<{ | ||
card: SocialPreviewResolved | ||
}>() | ||
</script> | ||
|
||
<template> | ||
<div class="max-w-[524px] min-w-[524px] cursor-pointer"> | ||
<div | ||
class="h-[274px] border border-b-0 border-base bg-cover bg-center bg-no-repeat" | ||
:style="{ backgroundImage: `url(${JSON.stringify(card.image)})` }" | ||
/> | ||
<div class="break-words border border-base px-[12px] py-[10px] antialiased"> | ||
<div class="overflow-hidden truncate whitespace-nowrap text-[12px] leading-[11px] uppercase op50"> | ||
{{ card.url }} | ||
</div><div class="block h-[46px] max-h-[46px] border-separate select-none overflow-hidden break-words text-left" style="border-spacing: 0px;"> | ||
<div class="mt-[3px] truncate pt-[2px] text-[16px] font-semibold leading-[20px]"> | ||
{{ card.title }} | ||
</div><div | ||
class="mt-[3px] block h-[18px] max-h-[80px] border-separate select-none overflow-hidden truncate whitespace-nowrap break-words text-left text-[14px] leading-[20px] op50" | ||
style="-webkit-line-clamp: 1; border-spacing: 0px; -webkit-box-orient: vertical;" | ||
> | ||
{{ card.description }} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</template> |
24 changes: 24 additions & 0 deletions
24
packages/devtools/client/components/social/SocialLinkedin.vue
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,24 @@ | ||
<script setup lang="ts"> | ||
import type { SocialPreviewResolved } from '~/../src/types' | ||
defineProps<{ | ||
card: SocialPreviewResolved | ||
}>() | ||
</script> | ||
|
||
<template> | ||
<div class="max-w-[520px] min-w-[520px] cursor-pointer overflow-hidden border border-base rounded-[2px] shadow-md"> | ||
<div | ||
class="h-[270px] border-b border-base bg-cover bg-center bg-no-repeat" | ||
:style="{ backgroundImage: `url(${JSON.stringify(card.image)})` }" | ||
/><div class="break-words p-[10px] antialiased"> | ||
<div class="block h-auto max-h-[50px] border-separate select-none break-words text-left" style="border-spacing: 0px;"> | ||
<div class="pb-[2px] text-[16px] font-semibold leading-[24px]"> | ||
{{ card.title }} | ||
</div><div class="overflow-hidden truncate whitespace-nowrap text-xs font-normal uppercase op85"> | ||
{{ card.url }} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</template> |
57 changes: 57 additions & 0 deletions
57
packages/devtools/client/components/social/SocialPreviewGroup.vue
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,57 @@ | ||
<script setup lang="ts"> | ||
import type { NormalizedHeadTag, SocialPreviewResolved } from '../../../src/types' | ||
const props = defineProps<{ | ||
tags: NormalizedHeadTag[] | ||
}>() | ||
const types = [ | ||
'twitter', | ||
'facebook', | ||
'linkedin', | ||
] | ||
const selected = ref(types[0]) | ||
const card = computed((): SocialPreviewResolved => { | ||
return { | ||
url: window.location.host, | ||
title: props.tags.find(tag => tag.tag === 'title')?.value, | ||
image: props.tags.find(tag => tag.tag === 'meta' && tag.name === 'og:image')?.value, | ||
imageAlt: props.tags.find(tag => tag.tag === 'meta' && tag.name === 'og:image:alt')?.value, | ||
description: props.tags.find(tag => tag.tag === 'meta' && tag.name === 'og:description')?.value, | ||
favicon: props.tags.find(tag => tag.tag === 'link' && tag.name === 'icon')?.value, | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div h-full w-max flex="~ col"> | ||
<div flex="~ wrap" w-full flex-none> | ||
<template v-for="name, idx of types" :key="idx"> | ||
<button | ||
px4 py2 border="r base" | ||
hover="bg-active" | ||
:class="name === selected ? '' : 'border-b'" | ||
@click="selected = name" | ||
> | ||
<div :class="name === selected ? '' : 'op30' " capitalize> | ||
{{ name }} | ||
</div> | ||
</button> | ||
</template> | ||
<div border="b base" flex-auto /> | ||
</div> | ||
<div flex="~ items-center justify-center" flex-auto p4> | ||
<div v-if="selected === 'facebook'"> | ||
<SocialFacebook :card="card" /> | ||
</div> | ||
<div v-else-if="selected === 'twitter'"> | ||
<SocialTwitter :tags="tags" /> | ||
</div> | ||
<div v-else-if="selected === 'linkedin'"> | ||
<SocialLinkedin :card="card" /> | ||
</div> | ||
</div> | ||
</div> | ||
</template> |
54 changes: 54 additions & 0 deletions
54
packages/devtools/client/components/social/SocialTwitter.vue
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,54 @@ | ||
<script setup lang="ts"> | ||
import type { NormalizedHeadTag, SocialPreviewResolved } from '~/../src/types' | ||
const props = defineProps<{ | ||
tags: NormalizedHeadTag[] | ||
}>() | ||
const card = computed((): SocialPreviewResolved => { | ||
return { | ||
url: window.location.host, | ||
title: props.tags.find(tag => tag.tag === 'meta' && tag.name === 'twitter:title')?.value || props.tags.find(tag => tag.tag === 'title')?.value, | ||
image: props.tags.find(tag => tag.tag === 'meta' && tag.name === 'twitter:image')?.value, | ||
imageAlt: props.tags.find(tag => tag.tag === 'meta' && tag.name === 'twitter:image:alt')?.value, | ||
description: props.tags.find(tag => tag.tag === 'meta' && tag.name === 'twitter:description')?.value || props.tags.find(tag => tag.tag === 'meta' && tag.name === 'description')?.value, | ||
favicon: props.tags.find(tag => tag.tag === 'link' && tag.name === 'icon')?.value, | ||
} | ||
}) | ||
const type = computed(() => { | ||
if (!card.value.image) | ||
return 'summary' | ||
return props.tags.find(tag => tag.tag === 'meta' && tag.name === 'twitter:card')?.value || 'summary_large_image' | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div | ||
class="max-w-[438px] min-w-[438px] cursor-pointer overflow-hidden border border-base rounded-[0.85714em] leading-[1.3em] -outline-offset-1" | ||
:class="type === 'summary_large_image' ? '' : 'flex'" | ||
hover="bg-[#88888805]" | ||
> | ||
<div | ||
v-if="type === 'summary_large_image'" | ||
class="h-[220px] border-b border-base bg-cover bg-center bg-no-repeat" | ||
:style="{ backgroundImage: `url(${JSON.stringify(card.image)})` }" | ||
/> | ||
<div | ||
v-else | ||
class="h-[122px] w-[122px] flex-none border-r border-base bg-cover bg-center bg-no-repeat" | ||
:style="{ backgroundImage: `url(${JSON.stringify(card.image)})` }" | ||
/> | ||
<div class="break-words border-base p-[0.75em] antialiased" flex="~ col justify-center gap-1"> | ||
<div class="mt-[0.32333em] overflow-hidden truncate whitespace-nowrap text-[14px] leading-[18px] lowercase op50"> | ||
{{ card.url }} | ||
</div> | ||
<div class="m-0 truncate text-[14px] font-semibold leading-[19px]"> | ||
{{ card.title }} | ||
</div> | ||
<div class="line-clamp-2 block select-none overflow-hidden break-words text-left text-[14px] leading-[18px] op50"> | ||
{{ card.description }} | ||
</div> | ||
</div> | ||
</div> | ||
</template> |
Oops, something went wrong.