feat(new-tool): meta tag generator

This commit is contained in:
Corentin Thomasset 2022-08-17 14:36:52 +02:00
parent 49755909bd
commit 164e32b442
No known key found for this signature in database
GPG key ID: 3103EB5E79496F9C
23 changed files with 549 additions and 41 deletions

View file

@ -33,10 +33,12 @@ import { useClipboard, useElementSize } from '@vueuse/core';
import hljs from 'highlight.js/lib/core';
import jsonHljs from 'highlight.js/lib/languages/json';
import sqlHljs from 'highlight.js/lib/languages/sql';
import xmlHljs from 'highlight.js/lib/languages/xml';
import { ref, toRefs } from 'vue';
hljs.registerLanguage('sql', sqlHljs);
hljs.registerLanguage('json', jsonHljs);
hljs.registerLanguage('html', xmlHljs);
const props = withDefaults(
defineProps<{

View file

@ -1,60 +1,62 @@
import {
create,
NAlert,
NAutoComplete,
NButton,
NConfigProvider,
NCard,
NInput,
NColorPicker,
NInputNumber,
NSpace,
NH1,
NForm,
NFormItem,
NTimePicker,
NText,
NIcon,
NSwitch,
NCollapseTransition,
NGrid,
NGridItem,
NPopconfirm,
NSlider,
NCode,
NCollapse,
NCollapseItem,
NProgress,
NAutoComplete,
NSelect,
NUpload,
NEmpty,
NModal,
NTooltip,
NAlert,
NP,
NH2,
NCollapseTransition,
NColorPicker,
NConfigProvider,
NDatePicker,
NDivider,
NDropdown,
NDynamicInput,
NEllipsis,
NEmpty,
NForm,
NFormItem,
NGradientText,
NGrid,
NGridItem,
NH1,
NH2,
NH3,
NIcon,
NImage,
NInput,
NInputGroup,
NInputGroupLabel,
NInputNumber,
NLayout,
NLayoutSider,
NMenu,
NMessageProvider,
NModal,
NP,
NPageHeader,
NPopconfirm,
NProgress,
NResult,
NH3,
NEllipsis,
NTag,
NInputGroup,
NInputGroupLabel,
NDivider,
NStatistic,
NTable,
NUploadDragger,
NImage,
NScrollbar,
NGradientText,
NCode,
NDatePicker,
NSelect,
NSlider,
NSpace,
NStatistic,
NSwitch,
NTable,
NTag,
NText,
NTimePicker,
NTooltip,
NUpload,
NUploadDragger,
} from 'naive-ui';
const components = [
NDynamicInput,
NDatePicker,
NCode,
NGradientText,

View file

@ -22,6 +22,7 @@ import { tool as baseConverter } from './integer-base-converter';
import { tool as jsonViewer } from './json-viewer';
import { tool as loremIpsumGenerator } from './lorem-ipsum-generator';
import { tool as mathEvaluator } from './math-evaluator';
import { tool as metaTagGenerator } from './meta-tag-generator';
import { tool as qrCodeGenerator } from './qr-code-generator';
import { tool as randomPortGenerator } from './random-port-generator';
import { tool as romanNumeralConverter } from './roman-numeral-converter';
@ -55,7 +56,7 @@ export const toolsByCategory: ToolCategory[] = [
{
name: 'Web',
icon: LockOpen,
components: [urlEncoder, htmlEntities, urlParser, deviceInformation, basicAuthGenerator],
components: [urlEncoder, htmlEntities, urlParser, deviceInformation, basicAuthGenerator, metaTagGenerator],
},
{
name: 'Images',

View file

@ -0,0 +1,27 @@
import type { SelectGroupOption, SelectOption } from 'naive-ui';
export type { OGSchemaType, OGSchemaTypeElementInput, OGSchemaTypeElementSelect, OGSchemaTypeElementInputMultiple };
interface OGSchemaTypeElementBase {
key: string;
label: string;
placeholder: string;
}
interface OGSchemaTypeElementInput extends OGSchemaTypeElementBase {
type: 'input';
}
interface OGSchemaTypeElementInputMultiple extends OGSchemaTypeElementBase {
type: 'input-multiple';
}
interface OGSchemaTypeElementSelect extends OGSchemaTypeElementBase {
type: 'select';
options: Array<SelectOption | SelectGroupOption>;
}
interface OGSchemaType {
name: string;
elements: (OGSchemaTypeElementSelect | OGSchemaTypeElementInput | OGSchemaTypeElementInputMultiple)[];
}

View file

@ -0,0 +1,25 @@
import { Tags } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Open graph meta generator',
path: '/og-meta-generator',
description: 'Generate open-graph and socials html meta tags for your website.',
keywords: [
'meta',
'tag',
'generator',
'social',
'title',
'description',
'image',
'share',
'online',
'website',
'open',
'graph',
'og',
],
component: () => import('./meta-tag-generator.vue'),
icon: Tags,
});

View file

@ -0,0 +1,94 @@
<template>
<div>
<div v-for="{ name, elements } of sections" :key="name" style="margin-bottom: 15px">
<n-form-item :label="name" :show-feedback="false"> </n-form-item>
<n-input-group v-for="{ key, type, label, placeholder, ...element } of elements" :key="key">
<n-input-group-label style="flex: 0 0 110px">{{ label }}</n-input-group-label>
<n-input v-if="type === 'input'" v-model:value="metadata[key]" :placeholder="placeholder" />
<n-dynamic-input
v-else-if="type === 'input-multiple'"
v-model:value="metadata[key]"
:min="1"
:placeholder="placeholder"
:default-value="['']"
:show-sort-button="true"
/>
<n-select
v-else-if="type === 'select'"
v-model:value="metadata[key]"
:placeholder="placeholder"
:options="(element as OGSchemaTypeElementSelect).options"
/>
</n-input-group>
</div>
</div>
<div>
<n-form-item label="Your meta tags">
<textarea-copyable :value="metaTags" language="html" />
</n-form-item>
</div>
</template>
<script setup lang="ts">
import TextareaCopyable from '@/components/TextareaCopyable.vue';
import { generateMeta } from '@it-tools/oggen';
import _ from 'lodash';
import { computed, ref, watch } from 'vue';
import { image, ogSchemas, twitter, website } from './og-schemas';
import type { OGSchemaType, OGSchemaTypeElementSelect } from './OGSchemaType.type';
// Since type guards do not work in template
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const metadata = ref<{ type: string; [k: string]: any }>({
type: 'website',
'twitter:card': 'summary_large_image',
});
watch(
() => ref(metadata.value.type),
(_ignored, prevSection) => {
const section = ogSchemas[prevSection.value];
if (!section) return;
section.elements.forEach(({ key }) => {
metadata.value[key] = '';
});
},
);
const sections = computed(() => {
const secs: OGSchemaType[] = [website, image, twitter];
const additionalSchema = ogSchemas[metadata.value.type];
if (additionalSchema) secs.push(additionalSchema);
return secs;
});
const metaTags = computed(() => {
const twitterMeta = _.chain(metadata.value)
.pickBy((_value, k) => k.startsWith('twitter:'))
.mapKeys((_value, k) => k.replace(/^twitter:/, ''))
.value();
const otherMeta = _.pickBy(metadata.value, (_value, k) => !k.startsWith('twitter:'));
return generateMeta({ ...otherMeta, twitter: twitterMeta }, { generateTwitterCompatibleMeta: true });
});
</script>
<style lang="less" scoped>
.n-input-group {
margin-bottom: 5px;
}
::v-deep(.n-form-item-blank) {
min-height: 0 !important;
}
::v-deep(.n-dynamic-input-item) {
margin-bottom: 5px;
}
</style>

View file

@ -0,0 +1,33 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const article: OGSchemaType = {
name: 'Article',
elements: [
{
type: 'input',
label: 'Publishing date',
key: 'article:published_time',
placeholder: 'When the article was first published...',
},
{
type: 'input',
label: 'Modification date',
key: 'article:modified_time',
placeholder: 'When the article was last changed...',
},
{
type: 'input',
label: 'Expiration date',
key: 'article:expiration_time',
placeholder: 'When the article is out of date after...',
},
{ type: 'input', label: 'Author', key: 'article:author', placeholder: 'Writers of the article...' },
{
type: 'input',
label: 'Section',
key: 'article:section',
placeholder: 'A high-level section name. E.g. Technology..',
},
{ type: 'input', label: 'Tag', key: 'article:tag', placeholder: 'Tag words associated with this article...' },
],
};

View file

@ -0,0 +1,16 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const book: OGSchemaType = {
name: 'Book',
elements: [
{ type: 'input', label: 'Author', key: 'book:author', placeholder: 'Who wrote this book...' },
{ type: 'input', label: 'ISBN', key: 'book:isbn', placeholder: 'The International Standard Book Number...' },
{
type: 'input',
label: 'Release date',
key: 'book:release_date',
placeholder: 'The date the book was released...',
},
{ type: 'input', label: 'Tag', key: 'book:tag', placeholder: 'Tag words associated with this book...' },
],
};

View file

@ -0,0 +1,31 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const image: OGSchemaType = {
name: 'Image',
elements: [
{
type: 'input',
label: 'Image url',
placeholder: 'The url of your website social image...',
key: 'image',
},
{
type: 'input',
label: 'Image alt',
placeholder: 'The alternative text of your website social image...',
key: 'image:alt',
},
{
type: 'input',
label: 'Width',
placeholder: 'Width in px of your website social image...',
key: 'image:width',
},
{
type: 'input',
label: 'Height',
placeholder: 'Height in px of your website social image...',
key: 'image:height',
},
],
};

View file

@ -0,0 +1,31 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { article } from './article';
import { book } from './book';
import { musicAlbum } from './musicAlbum';
import { musicPlaylist } from './musicPlaylist';
import { musicRadioStation } from './musicRadioStation';
import { musicSong } from './musicSong';
import { profile } from './profile';
import { videoEpisode } from './videoEpisode';
import { videoMovie } from './videoMovie';
import { videoOther } from './videoOther';
import { videoTVShow } from './videoTVShow';
export * from './image';
export * from './twitter';
export * from './website';
export const ogSchemas: Record<string, OGSchemaType> = {
'music.song': musicSong,
'music.album': musicAlbum,
'music.playlist': musicPlaylist,
'music.radio_station': musicRadioStation,
'video.movie': videoMovie,
'video.episode': videoEpisode,
'video.tv_show': videoTVShow,
'video.other': videoOther,
profile,
article,
book,
};

View file

@ -0,0 +1,27 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const musicAlbum: OGSchemaType = {
name: 'Album details',
elements: [
{ type: 'input', label: 'Song', key: 'music:song', placeholder: 'The song on this album...' },
{
type: 'input',
label: 'Disc',
key: 'music:song:disc',
placeholder: 'The same as music:album:disc but in reverse...',
},
{
type: 'input',
label: 'Track',
key: 'music:song:track',
placeholder: 'The same as music:album:track but in reverse...',
},
{ type: 'input', label: 'Musician', key: 'music:musician', placeholder: 'The musician that made this song...' },
{
type: 'input',
label: 'Release date',
key: 'music:release_date',
placeholder: 'The date the album was released...',
},
],
};

View file

@ -0,0 +1,21 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const musicPlaylist: OGSchemaType = {
name: 'Playlist details',
elements: [
{ type: 'input', label: 'Song', key: 'music:song', placeholder: 'The song on this album...' },
{
type: 'input',
label: 'Disc',
key: 'music:song:disc',
placeholder: 'The same as music:album:disc but in reverse...',
},
{
type: 'input',
label: 'Track',
key: 'music:song:track',
placeholder: 'The same as music:album:track but in reverse...',
},
{ type: 'input', label: 'Creator', key: 'music:creator', placeholder: 'The creator of this playlist...' },
],
};

View file

@ -0,0 +1,8 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const musicRadioStation: OGSchemaType = {
name: 'Radio station details',
elements: [
{ type: 'input', label: 'Creator', key: 'music:creator', placeholder: 'The creator of this radio station...' },
],
};

View file

@ -0,0 +1,22 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const musicSong: OGSchemaType = {
name: 'Song details',
elements: [
{ type: 'input', label: 'Duration', placeholder: 'The duration of the song...', key: 'music:duration' },
{ type: 'input', label: 'Album', placeholder: 'The album this song is from...', key: 'music:album' },
{
type: 'input',
label: 'Disc',
placeholder: 'Which disc of the album this song is on...',
key: 'music:album:disk',
},
{ type: 'input', label: 'Track', placeholder: ' Which track this song is...', key: 'music:album:track' },
{
type: 'input-multiple',
label: 'Musician',
placeholder: 'The musician that made this song...',
key: 'music:musician',
},
],
};

View file

@ -0,0 +1,21 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const profile: OGSchemaType = {
name: 'Profile',
elements: [
{
type: 'input',
label: 'First name',
placeholder: 'Enter the first name of the person...',
key: 'profile:first_name',
},
{
type: 'input',
label: 'Last name',
placeholder: 'Enter the last name of the person...',
key: 'profile:last_name',
},
{ type: 'input', label: 'Username', placeholder: 'Enter the username of the person...', key: 'profile:username' },
{ type: 'input', label: 'Gender', placeholder: 'Enter the gender of the person...', key: 'profile:gender' },
],
};

View file

@ -0,0 +1,31 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const twitter: OGSchemaType = {
name: 'Twitter',
elements: [
{
type: 'select',
options: [
{ label: 'Summary', value: 'summary' },
{ label: 'Summary with large image', value: 'summary_large_image' },
{ label: 'Application', value: 'app' },
{ label: 'Player', value: 'player' },
],
label: 'Card type',
placeholder: 'The twitter card type...',
key: 'twitter:card',
},
{
type: 'input',
label: 'Site account',
placeholder: 'The name of the twitter account of the site (ex: @ittoolsdottech)...',
key: 'twitter:site',
},
{
type: 'input',
label: 'Creator acc.',
placeholder: 'The name of the twitter account of the creator (ex: @cthmsst)...',
key: 'twitter:creator',
},
],
};

View file

@ -0,0 +1,10 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { videoMovie } from './videoMovie';
export const videoEpisode: OGSchemaType = {
name: 'Video episode details',
elements: [
...videoMovie.elements,
{ type: 'input', label: 'Series', key: 'video:series', placeholder: 'Which series this episode belongs to...' },
],
};

View file

@ -0,0 +1,29 @@
import type { OGSchemaType } from '../OGSchemaType.type';
export const videoMovie: OGSchemaType = {
name: 'Movie details',
elements: [
{
type: 'input-multiple',
label: 'Actor',
key: 'video:actor',
placeholder: 'Name of the actress/actor...',
},
// { type: 'input', label: 'Actor role', key: 'video:actor:role', placeholder: 'The role they played...' },
{
type: 'input-multiple',
label: 'Director',
key: 'video:director',
placeholder: 'Name of the director...',
},
{ type: 'input-multiple', label: 'Writer', key: 'video:writer', placeholder: 'Writers of the movie...' },
{ type: 'input', label: 'Duration', key: 'video:duration', placeholder: "The movie's length in seconds..." },
{
type: 'input',
label: 'Release date',
key: 'video:release_date',
placeholder: 'The date the movie was released...',
},
{ type: 'input', label: 'Tag', key: 'video:tag', placeholder: 'Tag words associated with this movie...' },
],
};

View file

@ -0,0 +1,7 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { videoMovie } from './videoMovie';
export const videoOther: OGSchemaType = {
name: 'Other video details',
elements: [...videoMovie.elements],
};

View file

@ -0,0 +1,7 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { videoMovie } from './videoMovie';
export const videoTVShow: OGSchemaType = {
name: 'TV show details',
elements: [...videoMovie.elements],
};

View file

@ -0,0 +1,56 @@
import type { OGSchemaType } from '../OGSchemaType.type';
const typeOptions = [
{ label: 'Website', value: 'website' },
{ label: 'Article', value: 'article' },
{ label: 'Book', value: 'book' },
{ label: 'Profile', value: 'profile' },
{
type: 'group',
label: 'Music',
key: 'Music',
children: [
{ label: 'Song', value: 'music.song' },
{ label: 'Music album', value: 'music.album' },
{ label: 'Playlist', value: 'music.playlist' },
{ label: 'Radio station', value: 'music.radio_station' },
],
},
{
type: 'group',
label: 'Video',
key: 'Video',
children: [
{ label: 'Movie', value: 'video.movie' },
{ label: 'Episode', value: 'video.episode' },
{ label: 'TV show', value: 'video.tv_show' },
{ label: 'Other video', value: 'video.other' },
],
},
];
export const website: OGSchemaType = {
name: 'General information',
elements: [
{
type: 'select',
label: 'Page type',
placeholder: 'Select the type of your website...',
key: 'type',
options: typeOptions,
},
{ type: 'input', label: 'Title', placeholder: 'Enter the title of your website...', key: 'title' },
{
type: 'input',
label: 'Description',
placeholder: 'Enter the description of your website...',
key: 'description',
},
{
type: 'input',
label: 'Page URL',
placeholder: 'Enter the url of your website...',
key: 'url',
},
],
};