diff --git a/package.json b/package.json index 78a5ad75..efe9bea1 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@it-tools/bip39": "^0.0.4", + "@it-tools/oggen": "^1.3.0", "@vicons/material": "^0.12.0", "@vicons/tabler": "^0.12.0", "@vueuse/core": "^8.9.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da28d762..7b70f103 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,6 +2,7 @@ lockfileVersion: 5.4 specifiers: '@it-tools/bip39': ^0.0.4 + '@it-tools/oggen': ^1.3.0 '@rushstack/eslint-patch': ^1.1.4 '@types/bcryptjs': ^2.4.2 '@types/crypto-js': ^4.1.1 @@ -67,6 +68,7 @@ specifiers: dependencies: '@it-tools/bip39': 0.0.4 + '@it-tools/oggen': 1.3.0 '@vicons/material': 0.12.0 '@vicons/tabler': 0.12.0 '@vueuse/core': 8.9.4_vue@3.2.37 @@ -1448,6 +1450,10 @@ packages: nanoid: 3.3.4 dev: false + /@it-tools/oggen/1.3.0: + resolution: {integrity: sha512-hvPn0mDkbJvA6jDxV3Xw+MIermq+QyrQpUq86KkZXaWLfCIDnLVp3hBmdxpxU4NIckXj5/7eeVg5CvW0kpQBTw==} + dev: false + /@jridgewell/gen-mapping/0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue index 49fb96d1..12d189e5 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 46627985..981d787a 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, - 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, diff --git a/src/tools/index.ts b/src/tools/index.ts index a03f273b..80f1618c 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 00000000..8d09013b --- /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; +} + +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 00000000..c79b19f3 --- /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 00000000..ee5d8e77 --- /dev/null +++ b/src/tools/meta-tag-generator/meta-tag-generator.vue @@ -0,0 +1,94 @@ + + + + + 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 00000000..3d2ce925 --- /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 00000000..f01733fa --- /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 00000000..60a4c5a9 --- /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 00000000..9c3f100b --- /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 = { + '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 00000000..225423c2 --- /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 00000000..610dd8e1 --- /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 00000000..d4c1126b --- /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 00000000..018ce178 --- /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 00000000..b0bc189e --- /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 00000000..94a9834d --- /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 00000000..b8a036e3 --- /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 00000000..6d5b02d9 --- /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 00000000..5bc9679e --- /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 00000000..5180c03b --- /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 00000000..15053e3d --- /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', + }, + ], +};