diff options
author | 2022-08-17 14:36:52 +0200 | |
---|---|---|
committer | 2022-08-19 17:40:00 +0200 | |
commit | 164e32b4428b8dfaaddcefa06b767a8af94573a9 (patch) | |
tree | 104babab05662dbcb89e6f9e06ebc19a60957ecd /src | |
parent | 49755909bdaea9399e51b67fbd1a6d071acd3182 (diff) | |
download | it-tools-164e32b4428b8dfaaddcefa06b767a8af94573a9.tar.gz it-tools-164e32b4428b8dfaaddcefa06b767a8af94573a9.tar.zst it-tools-164e32b4428b8dfaaddcefa06b767a8af94573a9.zip |
feat(new-tool): meta tag generator
Diffstat (limited to 'src')
21 files changed, 537 insertions, 36 deletions
diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue index 49fb96d..12d189e 100644 --- a/src/components/TextareaCopyable.vue +++ b/src/components/TextareaCopyable.vue @@ -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<{ diff --git a/src/plugins/naive.plugin.ts b/src/plugins/naive.plugin.ts index 4662798..981d787 100644 --- a/src/plugins/naive.plugin.ts +++ b/src/plugins/naive.plugin.ts @@ -1,60 +1,62 @@ import { create, + NAlert, + NAutoComplete, NButton, - NConfigProvider, NCard, - NInput, + NCode, + NCollapse, + NCollapseItem, + NCollapseTransition, NColorPicker, - NInputNumber, - NSpace, - NH1, + NConfigProvider, + NDatePicker, + NDivider, + NDropdown, + NDynamicInput, + NEllipsis, + NEmpty, NForm, NFormItem, - NTimePicker, - NText, - NIcon, - NSwitch, - NCollapseTransition, + NGradientText, NGrid, NGridItem, - NPopconfirm, - NSlider, - NCollapse, - NCollapseItem, - NProgress, - NAutoComplete, - NSelect, - NUpload, - NEmpty, - NModal, - NTooltip, - NAlert, - NP, + NH1, NH2, - NDropdown, + NH3, + NIcon, + NImage, + NInput, + NInputGroup, + NInputGroupLabel, + NInputNumber, NLayout, NLayoutSider, NMenu, NMessageProvider, + NModal, + NP, NPageHeader, + NPopconfirm, + NProgress, NResult, - NH3, - NEllipsis, - NTag, - NInputGroup, - NInputGroupLabel, - NDivider, + NScrollbar, + NSelect, + NSlider, + NSpace, NStatistic, + NSwitch, NTable, + NTag, + NText, + NTimePicker, + NTooltip, + NUpload, NUploadDragger, - NImage, - NScrollbar, - NGradientText, - NCode, - NDatePicker, } from 'naive-ui'; const components = [ + NDynamicInput, NDatePicker, NCode, NGradientText, diff --git a/src/tools/index.ts b/src/tools/index.ts index a03f273..80f1618 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -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', diff --git a/src/tools/meta-tag-generator/OGSchemaType.type.ts b/src/tools/meta-tag-generator/OGSchemaType.type.ts new file mode 100644 index 0000000..8d09013 --- /dev/null +++ b/src/tools/meta-tag-generator/OGSchemaType.type.ts @@ -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)[]; +} diff --git a/src/tools/meta-tag-generator/index.ts b/src/tools/meta-tag-generator/index.ts new file mode 100644 index 0000000..c79b19f --- /dev/null +++ b/src/tools/meta-tag-generator/index.ts @@ -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, +}); diff --git a/src/tools/meta-tag-generator/meta-tag-generator.vue b/src/tools/meta-tag-generator/meta-tag-generator.vue new file mode 100644 index 0000000..ee5d8e7 --- /dev/null +++ b/src/tools/meta-tag-generator/meta-tag-generator.vue @@ -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> diff --git a/src/tools/meta-tag-generator/og-schemas/article.ts b/src/tools/meta-tag-generator/og-schemas/article.ts new file mode 100644 index 0000000..3d2ce92 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/article.ts @@ -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...' }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/book.ts b/src/tools/meta-tag-generator/og-schemas/book.ts new file mode 100644 index 0000000..f01733f --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/book.ts @@ -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...' }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/image.ts b/src/tools/meta-tag-generator/og-schemas/image.ts new file mode 100644 index 0000000..60a4c5a --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/image.ts @@ -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', + }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/index.ts b/src/tools/meta-tag-generator/og-schemas/index.ts new file mode 100644 index 0000000..9c3f100 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/index.ts @@ -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, +}; diff --git a/src/tools/meta-tag-generator/og-schemas/musicAlbum.ts b/src/tools/meta-tag-generator/og-schemas/musicAlbum.ts new file mode 100644 index 0000000..225423c --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/musicAlbum.ts @@ -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...', + }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/musicPlaylist.ts b/src/tools/meta-tag-generator/og-schemas/musicPlaylist.ts new file mode 100644 index 0000000..610dd8e --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/musicPlaylist.ts @@ -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...' }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/musicRadioStation.ts b/src/tools/meta-tag-generator/og-schemas/musicRadioStation.ts new file mode 100644 index 0000000..d4c1126 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/musicRadioStation.ts @@ -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...' }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/musicSong.ts b/src/tools/meta-tag-generator/og-schemas/musicSong.ts new file mode 100644 index 0000000..018ce17 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/musicSong.ts @@ -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', + }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/profile.ts b/src/tools/meta-tag-generator/og-schemas/profile.ts new file mode 100644 index 0000000..b0bc189 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/profile.ts @@ -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' }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/twitter.ts b/src/tools/meta-tag-generator/og-schemas/twitter.ts new file mode 100644 index 0000000..94a9834 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/twitter.ts @@ -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', + }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/videoEpisode.ts b/src/tools/meta-tag-generator/og-schemas/videoEpisode.ts new file mode 100644 index 0000000..b8a036e --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/videoEpisode.ts @@ -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...' }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/videoMovie.ts b/src/tools/meta-tag-generator/og-schemas/videoMovie.ts new file mode 100644 index 0000000..6d5b02d --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/videoMovie.ts @@ -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...' }, + ], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/videoOther.ts b/src/tools/meta-tag-generator/og-schemas/videoOther.ts new file mode 100644 index 0000000..5bc9679 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/videoOther.ts @@ -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], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/videoTVShow.ts b/src/tools/meta-tag-generator/og-schemas/videoTVShow.ts new file mode 100644 index 0000000..5180c03 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/videoTVShow.ts @@ -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], +}; diff --git a/src/tools/meta-tag-generator/og-schemas/website.ts b/src/tools/meta-tag-generator/og-schemas/website.ts new file mode 100644 index 0000000..15053e3 --- /dev/null +++ b/src/tools/meta-tag-generator/og-schemas/website.ts @@ -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', + }, + ], +}; |