mirror of
https://github.com/andatoshiki/toshiki-home-nuxt3.git
synced 2026-06-05 21:46:29 +00:00
init: initial commit for all project root sources
This commit is contained in:
commit
324edea85e
13
.editorconfig
Executable file
13
.editorconfig
Executable file
@ -0,0 +1,13 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
7
.env.example
Executable file
7
.env.example
Executable file
@ -0,0 +1,7 @@
|
||||
FIREBASE_API_KEY=""
|
||||
FIREBASE_PROJECT_ID=""
|
||||
FIREBASE_APP_ID=""
|
||||
|
||||
GOOGLE_ANALYTICS_ID=""
|
||||
LASTFM_API_KEY=""
|
||||
UMAMI_WEBSITE_ID=""
|
||||
98
.gitignore
vendored
Executable file
98
.gitignore
vendored
Executable file
@ -0,0 +1,98 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Auto generated components file for WebStorm
|
||||
.components.gen.js
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
||||
|
||||
# Others
|
||||
.yarn
|
||||
.netlify
|
||||
.pnpm
|
||||
1
.node-version
Executable file
1
.node-version
Executable file
@ -0,0 +1 @@
|
||||
16
|
||||
15
.prettierignore
Executable file
15
.prettierignore
Executable file
@ -0,0 +1,15 @@
|
||||
# cache
|
||||
cache
|
||||
|
||||
# built dist
|
||||
dist
|
||||
|
||||
# scripts folder
|
||||
scripts
|
||||
|
||||
# lock file
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# package file
|
||||
package.json
|
||||
9
.vscode/extensions.json
vendored
Executable file
9
.vscode/extensions.json
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"johnsoncodehk.volar",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"voorjaar.windicss-intellisense",
|
||||
"editorconfig.editorconfig"
|
||||
]
|
||||
}
|
||||
20
@types/runtimeConfig.d.ts
vendored
Executable file
20
@types/runtimeConfig.d.ts
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
/* Interfaces */
|
||||
export interface SponsorLinks {
|
||||
github: string
|
||||
}
|
||||
|
||||
export interface Social {
|
||||
discord: string
|
||||
twitter: string
|
||||
github: string
|
||||
instagram: string
|
||||
trello: string
|
||||
email: string
|
||||
}
|
||||
|
||||
declare module '@nuxt/types/config/runtime' {
|
||||
interface NuxtRuntimeConfig {
|
||||
social: Social
|
||||
sponsor: SponsorLinks
|
||||
}
|
||||
}
|
||||
4
@types/vue.shim.d.ts
vendored
Executable file
4
@types/vue.shim.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
declare module '*.vue' {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
21
LICENSE
Executable file
21
LICENSE
Executable file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023-current Anda Toshiki <hello@toshiki.dev>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
24
config/buildModules.ts
Executable file
24
config/buildModules.ts
Executable file
@ -0,0 +1,24 @@
|
||||
import { NuxtOptionsModule } from '@nuxt/types/config/module'
|
||||
|
||||
// Import module options
|
||||
import colorMode from './modules/colorMode'
|
||||
import windicss from './modules/windicss'
|
||||
import image from './modules/image'
|
||||
import googleAnalytics from './modules/googleAnalytics'
|
||||
import typescriptBuild from './modules/typescriptBuild'
|
||||
|
||||
// Dev mode
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
const BuildModules: NuxtOptionsModule[] = [
|
||||
'@nuxtjs/moment',
|
||||
['@nuxt/image', image],
|
||||
['nuxt-windicss', windicss],
|
||||
['@nuxtjs/color-mode', colorMode],
|
||||
['@nuxt/typescript-build', typescriptBuild],
|
||||
['@nuxtjs/google-analytics', googleAnalytics]
|
||||
]
|
||||
|
||||
if (isDev) BuildModules.unshift('nuxt-vite')
|
||||
|
||||
export default BuildModules
|
||||
1
config/components.ts
Executable file
1
config/components.ts
Executable file
@ -0,0 +1 @@
|
||||
export default ['~/components']
|
||||
14
config/constants.ts
Executable file
14
config/constants.ts
Executable file
@ -0,0 +1,14 @@
|
||||
export default {
|
||||
social: {
|
||||
twitter: 'https://twitter.com/andatoshiki/',
|
||||
github: 'https://github.com/andatoshiki/',
|
||||
telegram: 'https://andatoshiki.t.me',
|
||||
instagram: 'https://instagram.com/andatoshiki/',
|
||||
youtube: 'https://youtube.com/@andatoshiki',
|
||||
mastodon: 'https://mastodon.social/@andatoshiki',
|
||||
email: 'hello@toshiki.dev'
|
||||
},
|
||||
sponsor: {
|
||||
github: 'https://github.com/sponsors/andatoshiki'
|
||||
}
|
||||
}
|
||||
1
config/css.ts
Executable file
1
config/css.ts
Executable file
@ -0,0 +1 @@
|
||||
export default ['@/stylesheets/root']
|
||||
7
config/generate.ts
Executable file
7
config/generate.ts
Executable file
@ -0,0 +1,7 @@
|
||||
import { NuxtOptionsGenerate } from '@nuxt/types/config/generate'
|
||||
|
||||
const Generate: NuxtOptionsGenerate = {
|
||||
fallback: true
|
||||
}
|
||||
|
||||
export default Generate
|
||||
106
config/head.ts
Executable file
106
config/head.ts
Executable file
@ -0,0 +1,106 @@
|
||||
import { NuxtOptionsHead } from '@nuxt/types/config/head'
|
||||
|
||||
/* Define constants */
|
||||
const image = 'https://toshiki.dev/icon.png'
|
||||
const description =
|
||||
'Young JavaScript developer from Arizona, interested in languages, gaming, and programming, trying to improve his JavaScript skills!'
|
||||
|
||||
const Head: NuxtOptionsHead = {
|
||||
title: 'toshiki.dev',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{
|
||||
hid: 'description',
|
||||
name: 'description',
|
||||
content: description
|
||||
},
|
||||
/* Twitter */
|
||||
{
|
||||
hid: 'twitter:card',
|
||||
name: 'twitter:card',
|
||||
content: 'summary'
|
||||
},
|
||||
{
|
||||
hid: 'twitter:site',
|
||||
name: 'twitter:site',
|
||||
content: '@andatoshiki'
|
||||
},
|
||||
{
|
||||
hid: 'twitter:creator',
|
||||
name: 'twitter:creator',
|
||||
content: '@andatoshiki'
|
||||
},
|
||||
{
|
||||
hid: 'twitter:title',
|
||||
name: 'twitter:title',
|
||||
content: 'andatoshiki'
|
||||
},
|
||||
{
|
||||
hid: 'twitter:description',
|
||||
name: 'twitter:description',
|
||||
content: description
|
||||
},
|
||||
{
|
||||
hid: 'twitter:image',
|
||||
name: 'twitter:image',
|
||||
content: image
|
||||
},
|
||||
/* Open-Graph */
|
||||
{
|
||||
hid: 'og:type',
|
||||
name: 'og:type',
|
||||
content: 'website'
|
||||
},
|
||||
{
|
||||
hid: 'og:site_name',
|
||||
name: 'og:site_name',
|
||||
content: 'toshiki.dev'
|
||||
},
|
||||
{
|
||||
hid: 'og:description',
|
||||
name: 'og:description',
|
||||
content: description
|
||||
},
|
||||
{
|
||||
hid: 'og:image',
|
||||
name: 'og:image',
|
||||
content: image
|
||||
},
|
||||
/* Others */
|
||||
{
|
||||
hid: 'theme-color',
|
||||
name: 'theme-color',
|
||||
content: '#171717'
|
||||
}
|
||||
].map(i => {
|
||||
// @ts-ignore-next-line
|
||||
if (i.name && !i.property) i.property = i.name
|
||||
return i
|
||||
}),
|
||||
link: [
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: 'https://toshiki.dev/assets/icons/favicon.ico'
|
||||
},
|
||||
{
|
||||
rel: 'search',
|
||||
type: 'application/opensearchdescription+xml',
|
||||
title: "'s Blog",
|
||||
href: 'https://toshiki.dev/opensearch.xml'
|
||||
},
|
||||
{ rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css' }
|
||||
],
|
||||
|
||||
script: [
|
||||
{
|
||||
async: true,
|
||||
defer: true,
|
||||
'data-website-id': `${process.env.UMAMI_WEBSITE_ID || ''}`,
|
||||
src: `${process.env.UMAMI_ENDPOINT || ''}`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default Head
|
||||
7
config/loading.ts
Executable file
7
config/loading.ts
Executable file
@ -0,0 +1,7 @@
|
||||
import { NuxtOptionsLoading } from '@nuxt/types/config/loading'
|
||||
|
||||
const Loading: NuxtOptionsLoading = {
|
||||
color: '#f3f4f6'
|
||||
}
|
||||
|
||||
export default Loading
|
||||
22
config/modules.ts
Executable file
22
config/modules.ts
Executable file
@ -0,0 +1,22 @@
|
||||
import { NuxtOptionsModule } from '@nuxt/types/config/module'
|
||||
|
||||
/* Import module options */
|
||||
import content from './modules/content'
|
||||
import feed from './modules/feed'
|
||||
import firebase from './modules/firebase'
|
||||
import pwa from './modules/pwa'
|
||||
import sitemap from './modules/sitemap'
|
||||
import webfontloader from './modules/webfontloader'
|
||||
|
||||
const Modules: NuxtOptionsModule[] = [
|
||||
'@nuxtjs/axios',
|
||||
'@nuxtjs/robots',
|
||||
['@nuxtjs/pwa', pwa],
|
||||
['@nuxt/content', content],
|
||||
['@nuxtjs/feed', feed],
|
||||
['@nuxtjs/sitemap', sitemap],
|
||||
['@nuxtjs/firebase', firebase],
|
||||
['nuxt-webfontloader', webfontloader]
|
||||
]
|
||||
|
||||
export default Modules
|
||||
10
config/modules/colorMode.ts
Executable file
10
config/modules/colorMode.ts
Executable file
@ -0,0 +1,10 @@
|
||||
import { ColorModeConfig } from '@nuxtjs/color-mode/types/color-mode'
|
||||
|
||||
const ColorMode: ColorModeConfig = {
|
||||
storageKey: 'color-mode',
|
||||
preference: 'system',
|
||||
fallback: 'dark',
|
||||
classSuffix: ''
|
||||
}
|
||||
|
||||
export default ColorMode
|
||||
32
config/modules/content.ts
Executable file
32
config/modules/content.ts
Executable file
@ -0,0 +1,32 @@
|
||||
import type { IContentOptions } from '@nuxt/content/types/index'
|
||||
|
||||
// prism highlighter
|
||||
const Content: IContentOptions = {
|
||||
liveEdit: false,
|
||||
dir: 'content',
|
||||
markdown: {
|
||||
prism: {
|
||||
theme: 'prism-themes/themes/prism-one-dark.css'
|
||||
},
|
||||
/* @ts-ignore-next-line */
|
||||
remarkExternalLinks: {
|
||||
target: '_blank',
|
||||
rel: 'noreferrer noopener'
|
||||
},
|
||||
remarkPlugins: [
|
||||
[
|
||||
'remark-autolink-headings',
|
||||
{
|
||||
behavior: 'append'
|
||||
}
|
||||
],
|
||||
'remark-math'
|
||||
],
|
||||
rehypePlugins: [
|
||||
// this next line here
|
||||
['rehype-katex', { output: 'html' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default Content
|
||||
48
config/modules/feed.ts
Executable file
48
config/modules/feed.ts
Executable file
@ -0,0 +1,48 @@
|
||||
const Feed = () => {
|
||||
const baseUrlArticles = 'https://toshiki.dev/blog'
|
||||
|
||||
const feedFormats = {
|
||||
rss: { type: 'rss2', file: 'rss.xml' },
|
||||
json: { type: 'json1', file: 'feed.json' }
|
||||
}
|
||||
|
||||
const { $content } = require('@nuxt/content')
|
||||
|
||||
const createFeedArticles = async function (feed: any) {
|
||||
feed.options = {
|
||||
title: "Toshiki's Blog",
|
||||
description: 'Real life stories anecdotes & developmental journeys for embarking your inspirations.',
|
||||
link: baseUrlArticles
|
||||
}
|
||||
|
||||
const articles = await $content('blog').fetch()
|
||||
|
||||
articles.forEach((article: any) => {
|
||||
const url = `${baseUrlArticles}/${article.slug}`
|
||||
|
||||
const hostName = process.env.NODE_ENV === 'production' ? 'https://toshiki.dev' : 'http://localhost:3000'
|
||||
|
||||
const postImagesPath = `${hostName}/assets/images/posts`
|
||||
|
||||
feed.addItem({
|
||||
title: article.title,
|
||||
slug: article.slug,
|
||||
link: url,
|
||||
image: article.image
|
||||
? `${hostName}${article.image}`
|
||||
: `${postImagesPath}/${url?.split('/')?.at(-1)}.jpg`,
|
||||
date: new Date(article.createdAt),
|
||||
description: article.description,
|
||||
content: article.summary
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return Object.values(feedFormats).map(({ file, type }) => ({
|
||||
path: `${file}`,
|
||||
create: createFeedArticles,
|
||||
type
|
||||
}))
|
||||
}
|
||||
|
||||
export default Feed
|
||||
31
config/modules/firebase.ts
Executable file
31
config/modules/firebase.ts
Executable file
@ -0,0 +1,31 @@
|
||||
import { FirebaseModuleConfiguration } from '@nuxtjs/firebase/types/index'
|
||||
import { config as loadEnv } from 'dotenv'
|
||||
|
||||
/* Load env variables */
|
||||
loadEnv()
|
||||
|
||||
const { FIREBASE_APP_ID: appId, FIREBASE_API_KEY: apiKey, FIREBASE_PROJECT_ID: projectId } = process.env
|
||||
|
||||
if (!appId || !apiKey || !projectId)
|
||||
throw new Error(
|
||||
'You are missing Firebase options, please check your .env file and make sure all required keys are present.'
|
||||
)
|
||||
|
||||
const Firebase: FirebaseModuleConfiguration = {
|
||||
config: {
|
||||
appId,
|
||||
apiKey,
|
||||
projectId,
|
||||
// Setting these because types aren't optional, though they're not required
|
||||
databaseURL: 'https://toshiki-home-nuxt-default-rtdb.asia-southeast1.firebasedatabase.app',
|
||||
authDomain: 'toshiki-home-nuxt.firebase.app',
|
||||
storageBucket: 'toshiki-home-nuxt.appspot.com',
|
||||
messagingSenderId: '765131615342',
|
||||
measurementId: 'G-GWD4JF3M6Z'
|
||||
},
|
||||
services: {
|
||||
firestore: true
|
||||
}
|
||||
}
|
||||
|
||||
export default Firebase
|
||||
7
config/modules/googleAnalytics.ts
Executable file
7
config/modules/googleAnalytics.ts
Executable file
@ -0,0 +1,7 @@
|
||||
import { InstallOptions } from 'vue-analytics'
|
||||
|
||||
const GoogleAnalytics: InstallOptions = {
|
||||
id: process.env.GOOGLE_ANALYTICS_ID || ''
|
||||
}
|
||||
|
||||
export default GoogleAnalytics
|
||||
14
config/modules/image.ts
Executable file
14
config/modules/image.ts
Executable file
@ -0,0 +1,14 @@
|
||||
const NuxtImage = {
|
||||
domains: [
|
||||
'https://http.toshiki.dev',
|
||||
'https://lastfm.freetls.fastly.net',
|
||||
'https://cdn.jsdelivr.net',
|
||||
'https://avatars.githubusercontent.com',
|
||||
'https://proxy.duckduckgo.com',
|
||||
'https://r2.toshiki.dev',
|
||||
'https://bucket.toshiki.dev',
|
||||
'https://jsd.toshiki.dev'
|
||||
]
|
||||
}
|
||||
|
||||
export default NuxtImage
|
||||
8
config/modules/pwa.ts
Executable file
8
config/modules/pwa.ts
Executable file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
manifest: {
|
||||
name: 'toshiki.dev',
|
||||
short_name: 'toshiki.dev',
|
||||
theme_color: '#f56565',
|
||||
lang: 'en'
|
||||
}
|
||||
}
|
||||
15
config/modules/sitemap.ts
Executable file
15
config/modules/sitemap.ts
Executable file
@ -0,0 +1,15 @@
|
||||
export default async function () {
|
||||
const { $content } = require('@nuxt/content')
|
||||
const posts = await $content('blog').fetch()
|
||||
|
||||
const routes = []
|
||||
for (const post of posts) {
|
||||
routes.push(`blog/${post.slug}`)
|
||||
}
|
||||
|
||||
return {
|
||||
hostname: 'https://toshiki.dev',
|
||||
gzip: true,
|
||||
routes
|
||||
}
|
||||
}
|
||||
8
config/modules/typescriptBuild.ts
Executable file
8
config/modules/typescriptBuild.ts
Executable file
@ -0,0 +1,8 @@
|
||||
import { Options } from '@nuxt/typescript-build'
|
||||
|
||||
const TypescriptBuild: Options = {
|
||||
// Disabling type checking since we have it on our editor and don't want it to slow down the build process
|
||||
typeCheck: false
|
||||
}
|
||||
|
||||
export default TypescriptBuild
|
||||
5
config/modules/vite.ts
Executable file
5
config/modules/vite.ts
Executable file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
experimentWarning: false,
|
||||
build: false,
|
||||
ssr: false
|
||||
}
|
||||
6
config/modules/webfontloader.ts
Executable file
6
config/modules/webfontloader.ts
Executable file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
custom: {
|
||||
families: ['Inter'],
|
||||
urls: ['https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap']
|
||||
}
|
||||
}
|
||||
8
config/modules/windicss.ts
Executable file
8
config/modules/windicss.ts
Executable file
@ -0,0 +1,8 @@
|
||||
import { resolve } from 'path'
|
||||
import { ModuleOptions } from 'nuxt-windicss/dist/types'
|
||||
|
||||
const windicss: ModuleOptions = {
|
||||
config: resolve('windi.config.ts')
|
||||
}
|
||||
|
||||
export default windicss
|
||||
30
config/plugins.ts
Executable file
30
config/plugins.ts
Executable file
@ -0,0 +1,30 @@
|
||||
import { NuxtOptionsPlugin } from '@nuxt/types/config/plugin'
|
||||
|
||||
const Plugins: NuxtOptionsPlugin[] = [
|
||||
'@/plugins/Util',
|
||||
'@/plugins/Types',
|
||||
'@/plugins/Disqus',
|
||||
{
|
||||
src: '@/plugins/CmdK',
|
||||
mode: 'client'
|
||||
},
|
||||
{
|
||||
src: '@/plugins/Lanyard',
|
||||
mode: 'client'
|
||||
},
|
||||
{
|
||||
src: '@/plugins/Firebase',
|
||||
mode: 'client'
|
||||
},
|
||||
{
|
||||
src: '@/plugins/Tippy',
|
||||
mode: 'client'
|
||||
},
|
||||
{
|
||||
src: '@/plugins/DPlayer',
|
||||
mode: 'client',
|
||||
ssr: false
|
||||
}
|
||||
]
|
||||
|
||||
export default Plugins
|
||||
7
config/publicRuntimeConfig.ts
Executable file
7
config/publicRuntimeConfig.ts
Executable file
@ -0,0 +1,7 @@
|
||||
/* Import constants */
|
||||
import constants from './constants'
|
||||
|
||||
export default {
|
||||
...constants,
|
||||
isDev: process.env.NODE_ENV === 'development'
|
||||
}
|
||||
47
hooks/generate/done.ts
Executable file
47
hooks/generate/done.ts
Executable file
@ -0,0 +1,47 @@
|
||||
import { existsSync, writeFileSync, mkdirSync } from 'fs'
|
||||
import consola from 'consola'
|
||||
import { join } from 'path'
|
||||
|
||||
// Scripts
|
||||
import { generateImage } from '../../scripts/generateOgImage'
|
||||
|
||||
// Functions
|
||||
import getReadingTime from '../../src/plugins/Utils/getReadingTime'
|
||||
|
||||
// english i18n
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric'
|
||||
})
|
||||
|
||||
export const generateDone = async (generator: any) => {
|
||||
const generateDir = generator.nuxt.options.generate.dir
|
||||
const folderPath = join(generateDir, './og-images/')
|
||||
|
||||
const { $content } = require('@nuxt/content')
|
||||
const articles = await $content('blog').fetch()
|
||||
|
||||
if (!articles.length) return
|
||||
|
||||
consola.info(`Generating OG images for ${articles.length} posts.`)
|
||||
|
||||
for (const article of articles) {
|
||||
const { title, description, slug, body, createdAt, tags } = article
|
||||
|
||||
const readingTime = getReadingTime(JSON.stringify(body))
|
||||
const postDate = formatter.format(new Date(createdAt)).split('.').join('/')
|
||||
|
||||
const metaImage = await generateImage({
|
||||
title,
|
||||
description,
|
||||
subtitles: [postDate, `${readingTime} Minutes`, `#${tags[0]}`]
|
||||
})
|
||||
|
||||
if (!existsSync(folderPath)) mkdirSync(folderPath)
|
||||
|
||||
writeFileSync(join(folderPath, `./${slug}.png`), metaImage)
|
||||
}
|
||||
|
||||
consola.success(`Generated ${articles.length} OG images.`)
|
||||
}
|
||||
13
jsconfig.json
Executable file
13
jsconfig.json
Executable file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./*"],
|
||||
"@/*": ["./src/*"],
|
||||
"~~/*": ["./*"],
|
||||
"@@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", ".nuxt", "dist"]
|
||||
}
|
||||
5
netlify.toml
Executable file
5
netlify.toml
Executable file
@ -0,0 +1,5 @@
|
||||
[[redirects]]
|
||||
force = true
|
||||
from = "/blog/gonderi/:slug"
|
||||
status = 301
|
||||
to = "/blog/:slug"
|
||||
94
netlify/functions/getLastFmSongs.ts
Executable file
94
netlify/functions/getLastFmSongs.ts
Executable file
@ -0,0 +1,94 @@
|
||||
import { Handler } from '@netlify/functions'
|
||||
import LastFMTyped from 'lastfm-typed'
|
||||
|
||||
// Can also be set through Netlify environment variables
|
||||
const LASTFM_API_KEY = process.env.LASTFM_API_KEY
|
||||
const username = 'andatoshiki'
|
||||
|
||||
const handler: Handler = async () => {
|
||||
if (!LASTFM_API_KEY)
|
||||
return {
|
||||
statusCode: 401
|
||||
}
|
||||
|
||||
try {
|
||||
const lastFm = new LastFMTyped(LASTFM_API_KEY)
|
||||
|
||||
const [info, topTracks, topArtists, recentTracks] = [
|
||||
await lastFm.user.getInfo(username),
|
||||
await lastFm.user.getTopTracks(username, { limit: 6, period: '7day' }),
|
||||
await lastFm.user.getTopArtists(username, { limit: 4, period: '7day' }),
|
||||
await lastFm.user.getRecentTracks(username, { limit: 15 })
|
||||
]
|
||||
|
||||
// Origin for CORS
|
||||
const origin = process.env.NODE_ENV === 'production' ? 'https://toshiki.dev' : 'http://localhost:*'
|
||||
|
||||
// Map track function
|
||||
const mapTrack = (track: any): any => {
|
||||
const artist = typeof track.artist === 'string' ? track.artist : track.artist.name
|
||||
|
||||
const object: any = {
|
||||
artist,
|
||||
name: track.name,
|
||||
image: track.image.find((image: any) => image.size === 'large')?.url,
|
||||
url: track.url,
|
||||
date: track.date?.uts,
|
||||
nowPlaying: track.nowplaying
|
||||
}
|
||||
|
||||
if (track.playcount) object.plays = track.playcount
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
// Map artist function
|
||||
const mapArtist = (artist: any) => {
|
||||
const object: any = {
|
||||
name: artist.name,
|
||||
image: artist.image.find((image: any) => image.size === 'large')?.url,
|
||||
url: artist.url
|
||||
}
|
||||
|
||||
if (artist.playcount) object.plays = artist.playcount
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
// Formatted user info
|
||||
const formattedUserInfo = {
|
||||
name: info.name,
|
||||
image: info.image.find(image => image.size === 'large')?.url,
|
||||
url: info.url,
|
||||
totalPlays: info.playcount,
|
||||
registered: info.registered
|
||||
}
|
||||
|
||||
// Return
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: false,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Allow-Methods': 'GET'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user: formattedUserInfo,
|
||||
recentTracks: recentTracks?.tracks?.map(mapTrack) || [],
|
||||
topTracks: topTracks?.tracks?.map(mapTrack) || [],
|
||||
topArtists: topArtists?.artists?.map(mapArtist) || []
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(error)
|
||||
|
||||
return {
|
||||
error: true,
|
||||
statusCode: error.statusCode || 500,
|
||||
message: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { handler }
|
||||
63
nuxt.config.ts
Executable file
63
nuxt.config.ts
Executable file
@ -0,0 +1,63 @@
|
||||
// Types
|
||||
import type { NuxtConfig } from '@nuxt/types'
|
||||
|
||||
// Base config
|
||||
import buildModules from './config/buildModules'
|
||||
import components from './config/components'
|
||||
import generate from './config/generate'
|
||||
import css from './config/css'
|
||||
import head from './config/head'
|
||||
import loading from './config/loading'
|
||||
import modules from './config/modules'
|
||||
import plugins from './config/plugins'
|
||||
import publicRuntimeConfig from './config/publicRuntimeConfig'
|
||||
|
||||
// Specific module options
|
||||
import vite from './config/modules/vite'
|
||||
import feed from './config/modules/feed'
|
||||
|
||||
// Hooks
|
||||
import { generateDone } from './hooks/generate/done'
|
||||
|
||||
// Constants
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
const Config: NuxtConfig = {
|
||||
// Constant options
|
||||
rootDir: './',
|
||||
srcDir: 'src',
|
||||
target: 'static',
|
||||
|
||||
/*
|
||||
Disabling server-side rendering on development mode because
|
||||
Vite module currently doesn't work when SSR is enabled. This
|
||||
might cause some issues and/or hydration errors but will be
|
||||
effective enough to help you develop easier.
|
||||
*/
|
||||
ssr: !isDev,
|
||||
|
||||
// Imported options
|
||||
head,
|
||||
loading,
|
||||
buildModules,
|
||||
components,
|
||||
generate,
|
||||
css,
|
||||
modules,
|
||||
plugins,
|
||||
publicRuntimeConfig,
|
||||
|
||||
hooks: {
|
||||
generate: {
|
||||
async done(generator) {
|
||||
await generateDone(generator)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Modules
|
||||
vite,
|
||||
feed
|
||||
}
|
||||
|
||||
export default Config
|
||||
94
package.json
Executable file
94
package.json
Executable file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"version": "6.0.0",
|
||||
"homepage": "https://toshiki.dev",
|
||||
"engines": {
|
||||
"node": ">=16.9"
|
||||
},
|
||||
"author": {
|
||||
"name": "Anda Toshiki",
|
||||
"email": "hello@toshiki.dev",
|
||||
"url": "https://toshiki.dev"
|
||||
},
|
||||
"bugs": {
|
||||
"email": "hello@toshiki.dev",
|
||||
"url": "https://github.com/andatoshiki/toshiki-home-nuxt3/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/andatoshiki/toshiki-home-nuxt3.git"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://toshiki.dev/donate"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/andatoshiki"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start",
|
||||
"generate": "nuxt generate",
|
||||
"lint": "prettier --write .",
|
||||
"function": "netlify functions:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@andatoshiki/vue-lanyard": "^0.0.0",
|
||||
"@netlify/functions": "^1.4.0",
|
||||
"@nuxt/content": "1.15.1",
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
"@nuxtjs/feed": "^2.0.0",
|
||||
"@nuxtjs/pwa": "^3.3.5",
|
||||
"@nuxtjs/robots": "^2.5.0",
|
||||
"@nuxtjs/sitemap": "^2.4.0",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@vue/runtime-dom": "^3.2.47",
|
||||
"core-js": "^3.30.1",
|
||||
"lastfm-typed": "^2.1.0",
|
||||
"medium-zoom": "^1.0.8",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"nuxt-vite": "^0.3.5",
|
||||
"nuxt-webfontloader": "^1.1.0",
|
||||
"prism-themes": "^1.9.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"sass": "^1.62.0",
|
||||
"vue-cmd-menu": "^0.1.9",
|
||||
"vue-disqus": "^4.0.1",
|
||||
"vue-tippy": "^4.16.0",
|
||||
"webpack": "^4.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
"@nuxt/image": "^0.7.1",
|
||||
"@nuxt/types": "^2.16.3",
|
||||
"@nuxt/typescript-build": "^3.0.1",
|
||||
"@nuxtjs/color-mode": "2.1.1",
|
||||
"@nuxtjs/firebase": "^8.2.2",
|
||||
"@nuxtjs/google-analytics": "^2.4.0",
|
||||
"@nuxtjs/moment": "^1.6.1",
|
||||
"@types/react": "^18.0.37",
|
||||
"@types/sharp": "^0.31.1",
|
||||
"client": "link:@@waline/client",
|
||||
"dotenv": "^16.0.3",
|
||||
"dplayer": "^1.27.1",
|
||||
"firebase": "^10.11.0",
|
||||
"isomorphic-unfetch": "^4.0.2",
|
||||
"netlify-cli": "^17.22.1",
|
||||
"nuxt": "^2.17.0",
|
||||
"nuxt-windicss": "^2.6.1",
|
||||
"postcss-preset-env": "^8.3.2",
|
||||
"prettier": "^2.8.7",
|
||||
"rehype-katex": "^5.0.0",
|
||||
"remark-math": "^4.0.0",
|
||||
"sass-loader": "10.1.1",
|
||||
"satori": "^0.4.13",
|
||||
"sharp": "^0.32.0",
|
||||
"vite": "^4.2.2",
|
||||
"vue-dplayer": "^0.0.10"
|
||||
}
|
||||
}
|
||||
20510
pnpm-lock.yaml
Normal file
20510
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
48
prettier.config.js
Executable file
48
prettier.config.js
Executable file
@ -0,0 +1,48 @@
|
||||
module.exports = {
|
||||
tabWidth: 4,
|
||||
printWidth: 120,
|
||||
proseWrap: 'preserve',
|
||||
semi: false,
|
||||
trailingComma: 'none',
|
||||
singleQuote: true,
|
||||
arrowParens: 'avoid',
|
||||
endOfLine: 'lf',
|
||||
overrides: [
|
||||
{
|
||||
files: '{*.md,.prettierrc,.stylelintrc,.babelrc,.html}',
|
||||
options: {
|
||||
tabWidth: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
files: '{*.js?(on),*.js, *.css, *.scss, *.vue}',
|
||||
options: {
|
||||
trailingComma: 'none',
|
||||
tabWidth: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
files: '{*.ts}',
|
||||
options: {
|
||||
trailingComma: 'none',
|
||||
tabWidth: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
files: '{**/.vscode/*.json,**/tsconfig.json,**/tsconfig.*.json}',
|
||||
options: {
|
||||
parser: 'json5',
|
||||
quoteProps: 'preserve',
|
||||
singleQuote: false,
|
||||
trailingComma: 'all',
|
||||
tabWidth: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
files: '*.y?(a)ml,',
|
||||
options: {
|
||||
singleQuote: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
147
scripts/generateOgImage.ts
Executable file
147
scripts/generateOgImage.ts
Executable file
@ -0,0 +1,147 @@
|
||||
import satori from "satori"
|
||||
import { readFileSync } from "fs"
|
||||
import sharp from "sharp"
|
||||
|
||||
// Polyfill
|
||||
import "isomorphic-unfetch"
|
||||
|
||||
// Fonts
|
||||
const Inter = readFileSync("./src/assets/fonts/Inter-Regular.ttf")
|
||||
const InterBold = readFileSync("./src/assets/fonts/Inter-Bold.ttf")
|
||||
|
||||
interface IMetaImage {
|
||||
title: string
|
||||
description: string
|
||||
subtitles?: string[]
|
||||
}
|
||||
|
||||
const emojis = [
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f4ab.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f636.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f44f.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f973.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/2728.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/2709.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/2600.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f30d.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f4a5.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f525.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f929.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/26a1.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/231b.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/2b50.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f4eb.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f4a1.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f4ad.png",
|
||||
"https://cdnjs.toshiki.dev/ajax/libs/twemoji/14.0.2/72x72/1f389.png",
|
||||
]
|
||||
|
||||
export const generateImage = async ({
|
||||
title,
|
||||
description,
|
||||
subtitles,
|
||||
}: IMetaImage) => {
|
||||
const svg = await satori(
|
||||
{
|
||||
type: "div",
|
||||
key: 1,
|
||||
props: {
|
||||
style: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-end",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 24,
|
||||
backgroundImage:
|
||||
"url(https://raw.toshiki.dev/andatoshiki/toshiki-home-nuxt/master/src/static/assets/meta/images/post-background.png)",
|
||||
},
|
||||
children: {
|
||||
type: "div",
|
||||
props: {
|
||||
style: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginLeft: 40,
|
||||
marginRight: 40,
|
||||
marginBottom: 40,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: "img",
|
||||
props: {
|
||||
src: emojis[Math.floor(Math.random() * emojis.length)],
|
||||
height: 80,
|
||||
width: 80,
|
||||
style: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "h1",
|
||||
props: {
|
||||
style: { fontSize: 60, fontWeight: 600, marginBottom: 0 },
|
||||
children: title,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "p",
|
||||
props: {
|
||||
style: { fontSize: 35, color: "rgba(0, 0, 0, 0.5)" },
|
||||
children: description,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "div",
|
||||
props: {
|
||||
style: {
|
||||
display: "flex",
|
||||
marginTop: 12,
|
||||
fontSize: 25,
|
||||
},
|
||||
children:
|
||||
subtitles?.map((item, index) => ({
|
||||
type: "span",
|
||||
props: {
|
||||
style: {
|
||||
borderRadius: 12,
|
||||
padding: "6px 16px",
|
||||
marginLeft: index === 0 ? 0 : 14,
|
||||
color: "rgba(0, 0, 0, 0.5)",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||||
},
|
||||
children: item,
|
||||
},
|
||||
})) || [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
width: 1200,
|
||||
height: 675,
|
||||
fonts: [
|
||||
{
|
||||
name: "Inter",
|
||||
data: Buffer.from(Inter),
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: Buffer.from(InterBold),
|
||||
weight: 700,
|
||||
style: "normal",
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
const png = await sharp(Buffer.from(svg)).png().toBuffer()
|
||||
return png
|
||||
}
|
||||
68
src/assets/files/premid/largeImages.ts
Executable file
68
src/assets/files/premid/largeImages.ts
Executable file
@ -0,0 +1,68 @@
|
||||
const general = [
|
||||
{ name: 'Extreme Thinking', url: 'https://i.imgur.com/IJbthB1.png' },
|
||||
{ name: 'Angry Stickman', url: 'https://i.imgur.com/90lDk9T.png' },
|
||||
{ name: 'Read The Docs', url: 'https://i.imgur.com/PvrqhRJ.png' },
|
||||
{ name: 'Please Stop', url: 'https://i.imgur.com/qgCcDu7.png' },
|
||||
{ name: 'Brilliance', url: 'https://i.imgur.com/skclu5h.png' },
|
||||
{ name: 'Pepe Sweat', url: 'https://i.imgur.com/j4ip0Dx.png' },
|
||||
{ name: 'Panda Cry', url: 'https://i.imgur.com/NFCmYsM.png' },
|
||||
{ name: 'Thinking', url: 'https://i.imgur.com/mzlKBw3.png' },
|
||||
{ name: 'Spongery', url: 'https://i.imgur.com/VfhmSfN.png' },
|
||||
{ name: 'Balance', url: 'https://i.imgur.com/v0jG4vt.png' },
|
||||
{ name: 'Bravery', url: 'https://i.imgur.com/etvIz6E.png' },
|
||||
{ name: 'Playing', url: 'https://i.imgur.com/UHgwoyQ.png' },
|
||||
{ name: 'Reading', url: 'https://i.imgur.com/tbSYfJV.png' },
|
||||
{ name: 'Popcorn', url: 'https://i.imgur.com/dQ4EOWV.png' },
|
||||
{ name: 'Windows', url: 'https://i.imgur.com/YkOU4HD.png' },
|
||||
{ name: 'Mobile', url: 'https://i.imgur.com/BhBThRQ.png' },
|
||||
{ name: 'Paused', url: 'https://i.imgur.com/TYvgF3M.png' },
|
||||
{ name: 'Search', url: 'https://i.imgur.com/hnDIQO1.png' },
|
||||
{ name: 'Please', url: 'https://i.imgur.com/Zp835mC.png' },
|
||||
{ name: 'Sadcat', url: 'https://i.imgur.com/IaSeSJk.png' },
|
||||
{ name: 'Coffee', url: 'https://i.imgur.com/W5NIvJF.png' },
|
||||
{ name: 'Doubt', url: 'https://i.imgur.com/kYKE2rI.png' },
|
||||
{ name: 'Relax', url: 'https://i.imgur.com/BaOQ4I8.png' },
|
||||
{ name: 'Smart', url: 'https://i.imgur.com/vKhMs2R.png' },
|
||||
{ name: 'Heart', url: 'https://i.imgur.com/jtt9fjf.png' },
|
||||
{ name: 'Shrug', url: 'https://i.imgur.com/UnzU96q.png' },
|
||||
{ name: 'Mmlol', url: 'https://i.imgur.com/5t2q2eu.png' },
|
||||
{ name: 'Linux', url: 'https://i.imgur.com/bN5rmiU.png' },
|
||||
{ name: 'Live', url: 'https://i.imgur.com/qphbAuR.png' },
|
||||
{ name: 'Cool', url: 'https://i.imgur.com/AdUBBHa.png' },
|
||||
{ name: 'Eyes', url: 'https://i.imgur.com/lIa90i4.png' },
|
||||
{ name: 'Ohno', url: 'https://i.imgur.com/7EkHsMr.png' },
|
||||
{ name: 'Tada', url: 'https://i.imgur.com/nO8fd9v.png' },
|
||||
{ name: 'ESL', url: 'https://i.imgur.com/F51eSEx.png' }
|
||||
]
|
||||
|
||||
const brand = [
|
||||
{ name: 'Facebook', url: 'https://i.imgur.com/5Aab3v6.png' },
|
||||
{ name: 'Instagram', url: 'https://i.imgur.com/1c5yuiD.png' },
|
||||
{ name: 'YouTube', url: 'https://i.imgur.com/0Bvl6BU.png' },
|
||||
{ name: 'YouTube Dark', url: 'https://i.imgur.com/mQQO1nv.jpg' },
|
||||
{ name: 'Netflix', url: 'https://i.imgur.com/DkZQvkC.png' },
|
||||
{ name: 'Twitter', url: 'https://i.imgur.com/AtV70mE.png' },
|
||||
{ name: 'Twitch', url: 'https://i.imgur.com/bmIsItf.png' },
|
||||
{ name: 'Discord', url: 'https://i.imgur.com/P6fs8jR.png' },
|
||||
{ name: 'Discord Templates', url: 'https://i.imgur.com/zqdRKw4.png' }
|
||||
]
|
||||
|
||||
/* Exports */
|
||||
export default [
|
||||
{
|
||||
name: 'General',
|
||||
items: general
|
||||
},
|
||||
{
|
||||
name: 'Brand',
|
||||
items: brand
|
||||
}
|
||||
]
|
||||
|
||||
export interface Type {
|
||||
name: string
|
||||
items: {
|
||||
name: string
|
||||
url: string
|
||||
}[]
|
||||
}
|
||||
48
src/assets/files/premid/smallImages.ts
Executable file
48
src/assets/files/premid/smallImages.ts
Executable file
@ -0,0 +1,48 @@
|
||||
const general = [
|
||||
{ name: 'DoNotDisturb', url: 'https://i.imgur.com/MvrnrMW.png' },
|
||||
{ name: 'Brilliance', url: 'https://i.imgur.com/skclu5h.png' },
|
||||
{ name: 'VideoCall', url: 'https://i.imgur.com/GwMdYmz.png' },
|
||||
{ name: 'Checkmark', url: 'https://i.imgur.com/FH7OH6y.png' },
|
||||
{ name: 'Thinking', url: 'https://i.imgur.com/mzlKBw3.png' },
|
||||
{ name: 'Windows', url: 'https://i.imgur.com/YkOU4HD.png' },
|
||||
{ name: 'NoEntry', url: 'https://i.imgur.com/jVidfcx.png' },
|
||||
{ name: 'Balance', url: 'https://i.imgur.com/v0jG4vt.png' },
|
||||
{ name: 'Bravery', url: 'https://i.imgur.com/etvIz6E.png' },
|
||||
{ name: 'Playing', url: 'https://i.imgur.com/UHgwoyQ.png' },
|
||||
{ name: 'Writing', url: 'https://i.imgur.com/4VcqgYA.png' },
|
||||
{ name: 'Reading', url: 'https://i.imgur.com/tbSYfJV.png' },
|
||||
{ name: 'Coffee', url: 'https://i.imgur.com/W5NIvJF.png' },
|
||||
{ name: 'Online', url: 'https://i.imgur.com/8cel80u.png' },
|
||||
{ name: 'Please', url: 'https://i.imgur.com/d10KCzD.png' },
|
||||
{ name: 'Paused', url: 'https://i.imgur.com/TYvgF3M.png' },
|
||||
{ name: 'Search', url: 'https://i.imgur.com/hnDIQO1.png' },
|
||||
{ name: 'Mmlol', url: 'https://i.imgur.com/5t2q2eu.png' },
|
||||
{ name: 'Heart', url: 'https://i.imgur.com/jtt9fjf.png' },
|
||||
{ name: 'Linux', url: 'https://i.imgur.com/bN5rmiU.png' },
|
||||
{ name: 'Live', url: 'https://i.imgur.com/qphbAuR.png' },
|
||||
{ name: 'Call', url: 'https://i.imgur.com/0akjqyz.png' },
|
||||
{ name: 'Idle', url: 'https://i.imgur.com/mKIQ8Zo.png' },
|
||||
{ name: 'Cool', url: 'https://i.imgur.com/AdUBBHa.png' },
|
||||
{ name: 'Tada', url: 'https://i.imgur.com/nO8fd9v.png' }
|
||||
]
|
||||
|
||||
const brand = [
|
||||
{ name: 'YouTube', url: 'https://i.imgur.com/0Bvl6BU.png' },
|
||||
{ name: 'YouTube Dark', url: 'https://i.imgur.com/mQQO1nv.jpg' },
|
||||
{ name: 'Netflix', url: 'https://i.imgur.com/DkZQvkC.png' },
|
||||
{ name: 'Twitter', url: 'https://i.imgur.com/AtV70mE.png' },
|
||||
{ name: 'Twitch', url: 'https://i.imgur.com/bmIsItf.png' },
|
||||
{ name: 'Discord', url: 'https://i.imgur.com/P6fs8jR.png' }
|
||||
]
|
||||
|
||||
/* Exports */
|
||||
export default [
|
||||
{
|
||||
name: 'General',
|
||||
items: general
|
||||
},
|
||||
{
|
||||
name: 'Brand',
|
||||
items: brand
|
||||
}
|
||||
]
|
||||
BIN
src/assets/fonts/Inter-Bold.ttf
Executable file
BIN
src/assets/fonts/Inter-Bold.ttf
Executable file
Binary file not shown.
BIN
src/assets/fonts/Inter-Regular.ttf
Executable file
BIN
src/assets/fonts/Inter-Regular.ttf
Executable file
Binary file not shown.
5
src/components/Blog/ImageContainer.vue
Executable file
5
src/components/Blog/ImageContainer.vue
Executable file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 md:(grid grid-flow-col auto-cols-fr)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
61
src/components/Blog/Notification.vue
Executable file
61
src/components/Blog/Notification.vue
Executable file
@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'information'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getIcon() {
|
||||
if (this.type === 'warning') return '❗️'
|
||||
else if (this.type === 'danger') return '🚨'
|
||||
else if (this.type === 'success') return '✅'
|
||||
else return '💡'
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="notification flex flex-col md:(items-center flex-row) gap-x-4 gap-y-2" :class="type">
|
||||
<span class="text-xl md:text-lg">{{ getIcon }}</span>
|
||||
|
||||
<div>
|
||||
<h1 v-if="title">{{ title }}</h1>
|
||||
|
||||
<p v-if="!!$slots.default">
|
||||
<slot />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notification,
|
||||
.nuxt-content .notification {
|
||||
@apply rounded-lg border-[0.1px] my-5 p-4 bg-opacity-25 bg-neutral-300 border-neutral-200 dark:(bg-neutral-800/30 border-neutral-800);
|
||||
|
||||
h1 {
|
||||
@apply font-medium text-lg m-0 hover:no-underline;
|
||||
}
|
||||
|
||||
p,
|
||||
p strong,
|
||||
a {
|
||||
@apply m-0 dark:text-white/70;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply font-medium text-current underline hover:underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
src/components/Blog/PrevNext.vue
Executable file
65
src/components/Blog/PrevNext.vue
Executable file
@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
/* Interfaces */
|
||||
import type { FetchReturn } from '@nuxt/content/types/query-builder'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
currentSlug: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prev: {} as FetchReturn,
|
||||
next: {} as FetchReturn
|
||||
}
|
||||
},
|
||||
async fetch() {
|
||||
const [prev, next] = (await this.$content('blog')
|
||||
.only(['title', 'slug'])
|
||||
.sortBy('createdAt', 'asc')
|
||||
.surround(this.currentSlug)
|
||||
.fetch()) as FetchReturn[]
|
||||
|
||||
this.prev = prev
|
||||
this.next = next
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div
|
||||
v-if="$fetchState.pending === false && !$fetchState.error"
|
||||
class="grid gap-x-4 gap-y-2 grid-cols-1 md:grid-cols-2"
|
||||
>
|
||||
<component
|
||||
:is="prev ? 'SmartLink' : 'div'"
|
||||
:href="prev && `/blog/${prev.slug}`"
|
||||
class="rounded-lg card-base flex items-center space-x-2"
|
||||
:class="!prev ? 'cursor-not-allowed' : 'dark:hover:text-white hover:bg-opacity-40'"
|
||||
>
|
||||
<IconChevron left class="h-4 w-4 flex-shrink-0" />
|
||||
|
||||
<span v-if="prev" class="truncate">{{ prev.title }}</span>
|
||||
<span v-else class="truncate">No former post</span>
|
||||
</component>
|
||||
|
||||
<component
|
||||
:is="next ? 'SmartLink' : 'div'"
|
||||
:href="next && `/blog/${next.slug}`"
|
||||
class="rounded-lg card-base flex items-center space-x-2 justify-end"
|
||||
:class="!next ? 'cursor-not-allowed' : 'dark:hover:text-white hover:bg-opacity-40'"
|
||||
>
|
||||
<span v-if="next" class="truncate">{{ next.title }}</span>
|
||||
<span v-else class="truncate">No latter post</span>
|
||||
|
||||
<IconChevron right class="h-4 w-4 flex-shrink-0" />
|
||||
</component>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
128
src/components/Blog/Rating.vue
Executable file
128
src/components/Blog/Rating.vue
Executable file
@ -0,0 +1,128 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
/* Interfaces */
|
||||
interface Platform {
|
||||
platform?: string
|
||||
classes?: string
|
||||
}
|
||||
|
||||
interface Status {
|
||||
component?: string
|
||||
title?: string
|
||||
classes?: string
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
rating: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
default: '0'
|
||||
},
|
||||
max: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: '10'
|
||||
},
|
||||
isnew: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
platform: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Returns platform according to the prop.
|
||||
* @returns {Platform}
|
||||
*/
|
||||
getPlatformInfo(): Platform {
|
||||
if (!this.platform) return {}
|
||||
|
||||
const platform = this.platform.toLowerCase()
|
||||
let classes
|
||||
|
||||
switch (platform) {
|
||||
case 'netflix':
|
||||
classes = 'text-red-600 bg-black'
|
||||
break
|
||||
case 'fox':
|
||||
classes = 'text-gray-100 bg-red-500'
|
||||
break
|
||||
case 'apple tv+':
|
||||
classes = 'text-white bg-black'
|
||||
break
|
||||
case 'tnt':
|
||||
classes = 'text-white bg-red-600'
|
||||
break
|
||||
case 'amazon-prime':
|
||||
classes = 'text-gray-100 bg-blue-500'
|
||||
break
|
||||
case 'disney+':
|
||||
classes = 'text-white bg-blue-900'
|
||||
break
|
||||
case 'adult-swim':
|
||||
classes = 'text-gray-100 bg-black'
|
||||
break
|
||||
case 'bbc':
|
||||
classes = 'text-gray-100 bg-black'
|
||||
break
|
||||
default:
|
||||
classes = 'bg-gray-200 dark:bg-neutral-800'
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
platform,
|
||||
classes
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center space-x-2 truncate">
|
||||
<div class="flex items-center flex-shrink-0 space-x-1">
|
||||
<!-- Channel Icon -->
|
||||
<IconChannel
|
||||
v-tippy="{
|
||||
content: platform,
|
||||
placement: 'top'
|
||||
}"
|
||||
:platform="getPlatformInfo.platform"
|
||||
class="flex-shrink-0 w-6 h-6 p-1 rounded-full focus:outline-none"
|
||||
:class="getPlatformInfo.classes"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-tippy="{
|
||||
content: `${rating}/${max} puan`,
|
||||
placement: 'top'
|
||||
}"
|
||||
class="rounded-md cursor-default flex font-medium bg-gray-200 flex-shrink-0 text-sm p-1 text-gray-700 w-12 items-center justify-center dark:(bg-neutral-800 text-gray-200) focus:outline-none"
|
||||
>
|
||||
{{ rating }} P
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-gray-900 truncate dark:text-gray-100" :class="{ new: isnew }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
a {
|
||||
@apply border-b border-black/10 transition-colors dark:(border-white/10 hover:border-white/30) hover:border-black/30;
|
||||
}
|
||||
|
||||
.new a {
|
||||
@apply border-blue-500 border-b-2 hover:border-blue-800;
|
||||
}
|
||||
</style>
|
||||
100
src/components/Blog/ReadingIndicator.vue
Executable file
100
src/components/Blog/ReadingIndicator.vue
Executable file
@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
selector: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
el: null as Element | null,
|
||||
scrollY: 0,
|
||||
rect: {
|
||||
top: 0,
|
||||
bottom: 0
|
||||
},
|
||||
window: {
|
||||
height: 0,
|
||||
width: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Calculates the position of the element and returns percentage value.
|
||||
*/
|
||||
getPercentLeftBottom(): number {
|
||||
const { top, bottom } = this.rect
|
||||
const percent = Math.round(((top - this.window.height) / (top - bottom)) * 100)
|
||||
|
||||
return percent > 100 ? 100 : percent
|
||||
},
|
||||
/**
|
||||
* Checks if the position is higher than a specific number and returns a boolean value.
|
||||
*/
|
||||
isElementVisible(): boolean {
|
||||
return this.scrollY > 175
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Find element in the document and set if exists
|
||||
const element = document.querySelector(this.selector)
|
||||
if (element) this.el = element
|
||||
else return
|
||||
|
||||
// Set window dimensions
|
||||
const { innerHeight, innerWidth } = window
|
||||
this.window = { height: innerHeight, width: innerWidth }
|
||||
|
||||
// Add scroll event to update positions
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// Remove scroll event before changing the page
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
},
|
||||
methods: {
|
||||
handleScroll() {
|
||||
// Set currenc scroll position
|
||||
this.scrollY = window.scrollY
|
||||
|
||||
// Set window height and width
|
||||
const { innerHeight, innerWidth } = window
|
||||
this.window = { height: innerHeight, width: innerWidth }
|
||||
|
||||
// Get element's position
|
||||
const { top, bottom } = this.el?.getBoundingClientRect() || {}
|
||||
|
||||
// Save element's position to Vue data
|
||||
if (!top || !bottom) return
|
||||
this.rect = { top, bottom }
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="isElementVisible"
|
||||
v-tippy="{
|
||||
content: getPercentLeftBottom === 100 ? 'Tüm yazı okundu!' : 'Okuma oranı'
|
||||
}"
|
||||
>
|
||||
<div class="rounded-md bg-gray-200 h-40 w-2 hidden relative md:block dark:bg-neutral-800">
|
||||
<div
|
||||
class="rounded-md inset-x-0 transition bottom-0 absolute"
|
||||
:class="{
|
||||
'bg-green-500': getPercentLeftBottom === 100,
|
||||
'bg-gray-300 dark:bg-neutral-600': getPercentLeftBottom < 100
|
||||
}"
|
||||
:style="{ height: `${getPercentLeftBottom}%` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
239
src/components/Blog/Sections/Ratings.vue
Executable file
239
src/components/Blog/Sections/Ratings.vue
Executable file
@ -0,0 +1,239 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
ratings: [
|
||||
{
|
||||
name: 'Daredevil',
|
||||
rating: 10,
|
||||
platform: 'Netflix',
|
||||
anchor: '#daredevil'
|
||||
},
|
||||
{
|
||||
name: 'Prison Break',
|
||||
rating: 10,
|
||||
platform: 'Fox',
|
||||
anchor: '#prison-break'
|
||||
},
|
||||
{
|
||||
name: 'Arcane',
|
||||
rating: 10,
|
||||
platform: 'Netflix',
|
||||
isNew: true,
|
||||
anchor: '#arcane'
|
||||
},
|
||||
{
|
||||
name: 'Love, Death & Robots',
|
||||
rating: 10,
|
||||
platform: 'Netflix',
|
||||
anchor: '#love-death--robots'
|
||||
},
|
||||
{
|
||||
name: 'Élite',
|
||||
rating: 9.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#élite'
|
||||
},
|
||||
{
|
||||
name: 'The Witcher',
|
||||
rating: 9.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#the-witcher'
|
||||
},
|
||||
{
|
||||
name: 'The Boys',
|
||||
rating: 9.5,
|
||||
platform: 'Amazon Prime',
|
||||
anchor: '#the-boys'
|
||||
},
|
||||
{
|
||||
name: 'Rise of Empires: Ottoman',
|
||||
rating: 9.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#rise-of-empires-ottoman'
|
||||
},
|
||||
{
|
||||
name: 'The Mandalorian',
|
||||
rating: 9.5,
|
||||
platform: 'Disney+',
|
||||
anchor: '#the-mandalorian'
|
||||
},
|
||||
{
|
||||
name: 'La Casa de Papel',
|
||||
rating: 9.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#la-casa-de-papel'
|
||||
},
|
||||
{
|
||||
name: 'Sex Education',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#sex-education'
|
||||
},
|
||||
{
|
||||
name: 'Locke & Key',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#locke--key'
|
||||
},
|
||||
{
|
||||
name: 'Stranger Things',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#stranger-things'
|
||||
},
|
||||
{
|
||||
name: 'See',
|
||||
rating: 9,
|
||||
platform: 'Apple TV+',
|
||||
anchor: '#see'
|
||||
},
|
||||
{
|
||||
name: 'Sherlock',
|
||||
rating: 9,
|
||||
platform: 'BBC',
|
||||
anchor: '#sherlock'
|
||||
},
|
||||
{
|
||||
name: 'Loki',
|
||||
rating: 9,
|
||||
platform: 'Disney+',
|
||||
isNew: true,
|
||||
anchor: '#loki'
|
||||
},
|
||||
{
|
||||
name: 'Lupin',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#lupin'
|
||||
},
|
||||
{
|
||||
name: 'Snowpiercer',
|
||||
rating: 9,
|
||||
platform: 'TNT',
|
||||
anchor: '#snowpiercer'
|
||||
},
|
||||
{
|
||||
name: 'The Haunting of Bly Manor',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#the-haunting-of-bly-manor'
|
||||
},
|
||||
{
|
||||
name: 'What If...?',
|
||||
rating: 9,
|
||||
platform: 'Disney+',
|
||||
isNew: true,
|
||||
anchor: '#what-if'
|
||||
},
|
||||
{
|
||||
name: 'When They See Us',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#when-they-see-us'
|
||||
},
|
||||
{
|
||||
name: 'Sense8',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#sense8'
|
||||
},
|
||||
{
|
||||
name: 'Chilling Adventures of Sabrina',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#chilling-adventures-of-sabrina'
|
||||
},
|
||||
{
|
||||
name: 'Altered Carbon',
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#altered-carbon'
|
||||
},
|
||||
{
|
||||
name: "The Queen's Gambit",
|
||||
rating: 9,
|
||||
platform: 'Netflix',
|
||||
anchor: '#the-queens-gambit'
|
||||
},
|
||||
{
|
||||
name: 'Aşk 101',
|
||||
rating: 8.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#aşk-101'
|
||||
},
|
||||
{
|
||||
name: 'The Order',
|
||||
rating: 8.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#the-order'
|
||||
},
|
||||
{
|
||||
name: 'BoJack Horseman',
|
||||
rating: 8.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#bojack-horseman'
|
||||
},
|
||||
{
|
||||
name: 'Rick and Morty',
|
||||
rating: 8.5,
|
||||
platform: 'Adult Swim',
|
||||
anchor: '#rick-and-morty'
|
||||
},
|
||||
{
|
||||
name: 'Lost in Space',
|
||||
rating: 8.5,
|
||||
platform: 'Netflix',
|
||||
anchor: '#lost-in-space'
|
||||
},
|
||||
{
|
||||
name: 'The Haunting of Hill House',
|
||||
rating: 8,
|
||||
platform: 'Netflix',
|
||||
anchor: '#the-haunting-of-hill-house'
|
||||
},
|
||||
{
|
||||
name: 'You',
|
||||
rating: 8,
|
||||
platform: 'Netflix',
|
||||
anchor: '#you'
|
||||
},
|
||||
{
|
||||
name: 'Lucifer',
|
||||
rating: 8,
|
||||
platform: 'Netflix',
|
||||
anchor: '#lucifer'
|
||||
},
|
||||
{
|
||||
name: 'The Umbrella Academy',
|
||||
rating: 8,
|
||||
platform: 'Netflix',
|
||||
anchor: '#the-umbrella-academy'
|
||||
}
|
||||
].sort((a, b) => b.rating - a.rating)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid gap-2 mb-6 lg:grid-cols-2">
|
||||
<BlogRating
|
||||
v-for="item in ratings"
|
||||
:key="item.name"
|
||||
:rating="item.rating"
|
||||
:platform="item.platform"
|
||||
:isnew="item.isNew"
|
||||
>
|
||||
<a :href="item.anchor">{{ item.name }}</a>
|
||||
</BlogRating>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
@apply text-current font-normal no-underline;
|
||||
}
|
||||
</style>
|
||||
100
src/components/Blog/Share.vue
Executable file
100
src/components/Blog/Share.vue
Executable file
@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
copied: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Creates a window or copies the URL.
|
||||
* @param {'url'|'twitter'|'telegram'|'whatsapp'} option The share option.
|
||||
*/
|
||||
share(option: 'url' | 'twitter' | 'telegram' | 'whatsapp') {
|
||||
if (option === 'url') {
|
||||
let el = this.$refs['share-url'] as HTMLInputElement
|
||||
|
||||
if (!el) {
|
||||
el = document.createElement('input')
|
||||
|
||||
el.value = this.path ? `https://toshiki.dev${this.path}` : location.href
|
||||
document.body.appendChild(el)
|
||||
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
} else {
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
}
|
||||
|
||||
this.copied = true
|
||||
setTimeout(() => (this.copied = false), 3000)
|
||||
} else {
|
||||
let url = ''
|
||||
|
||||
switch (option) {
|
||||
case 'twitter':
|
||||
url = `https://twitter.com/intent/tweet?via=andatoshiki&text=${encodeURIComponent(
|
||||
this.title + '\n' + location.href
|
||||
)}`
|
||||
break
|
||||
case 'telegram':
|
||||
url = `https://telegram.me/share/url?url=${encodeURIComponent(location.href)}`
|
||||
break
|
||||
case 'whatsapp':
|
||||
url = `https://api.whatsapp.com/send?text=${encodeURIComponent(
|
||||
this.title + '\n' + location.href
|
||||
)}`
|
||||
break
|
||||
}
|
||||
|
||||
window.open(url, `${option[0].toUpperCase() + option.toLowerCase().slice(1)}`, 'width=400,height=550')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Button rounded @click.native="share('twitter')">
|
||||
<IconBrand brand="twitter" class="text-[#1DA1F2] h-6 w-6" />
|
||||
</Button>
|
||||
|
||||
<Button rounded @click.native="share('telegram')">
|
||||
<IconBrand brand="telegram" class="text-[#2EAADE] h-6 w-6" />
|
||||
</Button>
|
||||
|
||||
<Button rounded @click.native="share('whatsapp')">
|
||||
<IconBrand brand="whatsapp" class="text-[#25D366] h-6 w-6" />
|
||||
</Button>
|
||||
|
||||
<Button rounded @click.native="share('url')">
|
||||
<IconCheck v-if="copied === true" class="text-green-500 h-6 w-6" />
|
||||
<IconLink v-else class="text-gray-800 dark:text-gray-200 h-6 w-6" />
|
||||
</Button>
|
||||
|
||||
<input ref="share-url" readonly :value="`https://toshiki.dev${path}`" class="hidden" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn svg {
|
||||
@apply h-8 w-8;
|
||||
}
|
||||
</style>
|
||||
49
src/components/Blog/TableOfContents.vue
Executable file
49
src/components/Blog/TableOfContents.vue
Executable file
@ -0,0 +1,49 @@
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue'
|
||||
|
||||
// Types
|
||||
import { Toc } from '~/src/types/Post'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
toc: {
|
||||
type: Array as PropType<Toc[]>,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tocToggled: false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="toc && toc.length > 0" class="rounded-md flex flex-col space-y-2 mb-6">
|
||||
<div
|
||||
class="cursor-pointer flex font-medium space-x-1 text-sm transition-colors text-gray-500 items-center dark:text-dark-100 hover:text-gray-700 dark:hover:text-white/40 select-none"
|
||||
@click="tocToggled = !tocToggled"
|
||||
>
|
||||
<h1 class="uppercase">Titile</h1>
|
||||
<transition name="fade" mode="out-in">
|
||||
<IconChevron v-if="!tocToggled" key="'tocToggled'" right class="h-4 w-4" />
|
||||
|
||||
<IconChevron v-else key="'tocToggledFalse'" down class="h-4 w-4" />
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<ul v-show="tocToggled === true" class="flex flex-wrap gap-2 items-center">
|
||||
<li
|
||||
v-for="link of toc || []"
|
||||
:key="link.id"
|
||||
class="border-b border-gray-300 text-sm transition-colors text-dark-400 dark:border-dark-200 dark:text-white/30 hover:text-dark-700 dark:hover:text-white/60"
|
||||
>
|
||||
<a v-if="link.id" :href="`#${link.id}`">
|
||||
{{ link.text }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
66
src/components/Button.vue
Executable file
66
src/components/Button.vue
Executable file
@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
// String
|
||||
href: {
|
||||
type: [] as PropType<any>,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
|
||||
// Boolean
|
||||
block: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
blank: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getIconName(): string {
|
||||
return this.icon?.startsWith('Icon') ? this.icon : `Icon${this.icon}`
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SmartLink
|
||||
:href="!disabled && href"
|
||||
:blank="blank"
|
||||
class="cursor-pointer justify-center px-5 py-2 rounded-lg card-base flex items-center space-x-2"
|
||||
:class="{
|
||||
'w-max': !block,
|
||||
'rounded-full': rounded
|
||||
}"
|
||||
>
|
||||
<component :is="getIconName" v-if="icon && !$slots.icon" class="h-4 w-4" />
|
||||
|
||||
<slot v-else name="icon" />
|
||||
|
||||
<span v-if="$slots.default">
|
||||
<slot />
|
||||
</span>
|
||||
</SmartLink>
|
||||
</template>
|
||||
303
src/components/Card/Discord.vue
Executable file
303
src/components/Card/Discord.vue
Executable file
@ -0,0 +1,303 @@
|
||||
<script lang="ts">
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
import Vue, { PropType } from 'vue'
|
||||
|
||||
/* Import image files */
|
||||
import largeImages from '@/assets/files/premid/largeImages'
|
||||
import smallImages from '@/assets/files/premid/smallImages'
|
||||
|
||||
/* Interfaces */
|
||||
interface ImageCategory {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Custom Status'
|
||||
},
|
||||
largeImage: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'PreMiD'
|
||||
},
|
||||
smallImage: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
smallImageText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
details: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
buttons: {
|
||||
type: Array as PropType<{ label: string; url: string }[]>,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
customImageUrl: {
|
||||
type: Object as PropType<{ small: string; large: string }>,
|
||||
required: false,
|
||||
default: () => ({ small: '', large: '' })
|
||||
},
|
||||
timestamp: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
componentReady: false,
|
||||
timers: {
|
||||
elapsed: {
|
||||
instance: null as NodeJS.Timeout | null,
|
||||
string: ''
|
||||
},
|
||||
end: {
|
||||
instance: null as NodeJS.Timeout | null,
|
||||
string: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Returns large and small image by replacing the spaces in their name.
|
||||
* @returns {{largeImage: string, smallImage: string}}
|
||||
*/
|
||||
getImages(): { largeImage: string; smallImage: string | null } {
|
||||
const { largeImage, smallImage } = this
|
||||
|
||||
/* Map arrays and combine items in all categories */
|
||||
const largeAll: ImageCategory[] = []
|
||||
const smallAll: ImageCategory[] = []
|
||||
|
||||
/* Loop into all arrays inside items and combine them in a single array */
|
||||
largeImages.map(item => item.items).forEach(category => largeAll.push(...category))
|
||||
|
||||
smallImages.map(item => item.items).forEach(category => smallAll.push(...category))
|
||||
|
||||
return {
|
||||
largeImage: this.customImageUrl.large
|
||||
? largeImage
|
||||
: largeAll.find(item => item.name === largeImage)?.url || 'https://i.imgur.com/CuVtvKW.png',
|
||||
smallImage: this.customImageUrl.small
|
||||
? smallImage
|
||||
: smallAll.find(item => item.name === smallImage)?.url || null
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns text related parts for the UI.
|
||||
* @returns {{details: string, state: string, small: string | undefined}}
|
||||
*/
|
||||
getText(): { details: string; state: string; small: string | undefined } {
|
||||
const { smallImage, smallImageText, details, state } = this
|
||||
|
||||
let small
|
||||
|
||||
if (smallImage && smallImageText) small = smallImageText
|
||||
else if (smallImage && !smallImageText) small = '[EMPTY]'
|
||||
|
||||
return {
|
||||
details,
|
||||
state,
|
||||
small
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Checks if timers are enabled, starts or stops timers according to passed props.
|
||||
* @returns {boolean} Whether any timer is enabled or not.
|
||||
*/
|
||||
isTimerEnabled(): boolean {
|
||||
const start = this?.timestamp?.start
|
||||
const end = this?.timestamp?.end
|
||||
|
||||
if (start?.enabled && start?.value) {
|
||||
this.startElapsedTimer()
|
||||
return true
|
||||
} else if (end?.enabled && end?.value) {
|
||||
this.startLeftTimer()
|
||||
return true
|
||||
} else {
|
||||
this.stopTimers()
|
||||
return false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns the string for enabled timer.
|
||||
* @returns {boolean |null | string}
|
||||
*/
|
||||
getTime(): boolean | null | string {
|
||||
if (this.isTimerEnabled === false) return null
|
||||
else if (this.timers.elapsed?.instance) return this.timers.elapsed.string
|
||||
else if (this.timers.end?.instance) return this.timers.end.string
|
||||
else return null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.componentReady = true
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopTimers()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Stops both of the timers.
|
||||
*/
|
||||
stopTimers() {
|
||||
const { elapsed, end } = this.timers
|
||||
|
||||
if (typeof elapsed === 'boolean' && typeof end === 'boolean') return
|
||||
|
||||
/* Clear elapsed timer */
|
||||
|
||||
// @ts-ignore-next-line
|
||||
clearInterval(elapsed.instance)
|
||||
elapsed.instance = null
|
||||
elapsed.string = ''
|
||||
|
||||
/* Clear end timer */
|
||||
|
||||
// @ts-ignore-next-line
|
||||
clearInterval(end.instance)
|
||||
end.instance = null
|
||||
end.string = ''
|
||||
},
|
||||
/**
|
||||
* Calculates the time difference between now and selected time and starts the elapsed timer.
|
||||
*/
|
||||
startElapsedTimer() {
|
||||
const target = this?.timestamp?.start?.value
|
||||
const timer = this?.timers?.elapsed
|
||||
|
||||
if (!target || !timer) return
|
||||
this.stopTimers()
|
||||
|
||||
timer.string = '00:00 elapsed'
|
||||
timer.instance = setInterval(() => {
|
||||
let timeArray = [
|
||||
String(this.$moment().diff(target, 'hours')),
|
||||
String(this.$moment().diff(target, 'minutes') % 60),
|
||||
String(this.$moment().diff(target, 'seconds') % 60)
|
||||
]
|
||||
|
||||
if (timeArray[0] === '0') timeArray = timeArray.slice(1)
|
||||
timeArray = timeArray.map(time => (time.length === 1 ? `0${time}` : time))
|
||||
|
||||
timer.string = `${timeArray.join(':')} elapsed`
|
||||
}, 1000)
|
||||
},
|
||||
/**
|
||||
* Calculates the time difference between now and selected time and starts the elapsed timer.
|
||||
*/
|
||||
startLeftTimer() {
|
||||
const target = this?.timestamp?.end?.value
|
||||
const timer = this?.timers?.end
|
||||
|
||||
if (!target || !timer) return
|
||||
this.stopTimers()
|
||||
|
||||
timer.string = '--:-- left'
|
||||
timer.instance = setInterval(() => {
|
||||
const toTime = this.$moment(target, 'HH:mm').unix()
|
||||
const fromTime = this.$moment().unix()
|
||||
const duration = this.$moment.duration(toTime - fromTime, 'seconds')
|
||||
|
||||
if (duration.asSeconds() < 0) return (timer.string = '00:00 left')
|
||||
|
||||
let timeArray = [String(duration.hours()), String(duration.minutes()), String(duration.seconds())]
|
||||
|
||||
if (timeArray[0] === '0') timeArray = timeArray.slice(1)
|
||||
timeArray = timeArray.map(time => (time.length === 1 ? `0${time}` : time))
|
||||
|
||||
timer.string = `${timeArray.join(':')} left`
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="componentReady"
|
||||
class="rounded-md bg-[#6c82cf] w-full py-4 px-6 overflow-x-hidden dark:bg-neutral-800/40"
|
||||
>
|
||||
<div class="pt-2">
|
||||
<h1 class="font-semibold text-xs text-white uppercase dark:text-gray-100">Playing a game</h1>
|
||||
|
||||
<div
|
||||
class="flex flex-col space-y-3 items-center justify-between overflow-x-hidden md:(space-y-0 space-x-3 flex-row)"
|
||||
>
|
||||
<div
|
||||
class="flex space-x-3 w-full py-2 items-center overflow-x-hidden md:space-x-5"
|
||||
:class="buttons.length > 0 && 'md:w-2/3'"
|
||||
>
|
||||
<div class="flex-shrink-0 h-32 w-32 relative">
|
||||
<SmartImage
|
||||
:key="getImages.largeImage"
|
||||
:src="getImages.largeImage"
|
||||
class="rounded-xl"
|
||||
alt="large image"
|
||||
height="256"
|
||||
width="256"
|
||||
/>
|
||||
|
||||
<SmartImage
|
||||
v-if="getImages.smallImage"
|
||||
:key="getImages.smallImage"
|
||||
v-tippy="{
|
||||
content: getText.small,
|
||||
placement: 'top'
|
||||
}"
|
||||
:src="getImages.smallImage"
|
||||
alt="small image"
|
||||
class="rounded-full bg-[#6c82cf] h-9 -right-2 -bottom-2 ring-4 ring-[#6c82cf] w-9 overflow-y-hidden absolute box-border dark:(bg-transparent ring-transparent) focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-gray-100 overflow-x-hidden">
|
||||
<h1 class="font-medium text-xl text-white block">{{ title }}</h1>
|
||||
|
||||
<div class="leading-tight">
|
||||
<span class="block truncate">{{ getText.details }}</span>
|
||||
<span class="block truncate">{{ getText.state }}</span>
|
||||
|
||||
<span v-if="isTimerEnabled === true" class="text-sm block">{{ getTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="buttons.length > 0" class="flex flex-col space-y-2 flex-shrink-0 md:w-1/3">
|
||||
<div v-for="(button, index) in buttons" :key="`button-${index}`" class="flex justify-end">
|
||||
<SmartLink
|
||||
:href="button.url"
|
||||
:title="button.url"
|
||||
class="border rounded-sm cursor-pointer border-white/40 text-sm py-2 px-4 transition-colors text-gray-300 truncate select-none md:(px-3 py-1) hover:(text-white border-white) focus:(bg-opacity-10 bg-white)"
|
||||
blank
|
||||
>{{ button.label }}</SmartLink
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skeleton load -->
|
||||
<div v-else class="rounded-md bg-[#6c82cf] h-[12.5rem] w-full animate-pulse" />
|
||||
</template>
|
||||
54
src/components/Card/Experience.vue
Executable file
54
src/components/Card/Experience.vue
Executable file
@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
date: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: new Date().getFullYear()
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
hiddenBadge: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SmartLink :href="url" blank>
|
||||
<div class="card-base leading-relaxed rounded-lg">
|
||||
<div class="flex space-x-2 items-center justify-between">
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<span class="text-black/50 dark:text-white/30 text-sm">{{ date }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="position"
|
||||
class="truncate text-sm text-black/50 dark:text-white/30"
|
||||
:class="hiddenBadge && 'flex items-center justify-between'"
|
||||
>
|
||||
{{ position }}
|
||||
<IconPlus v-if="hiddenBadge" class="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</SmartLink>
|
||||
</template>
|
||||
92
src/components/Card/Index.vue
Executable file
92
src/components/Card/Index.vue
Executable file
@ -0,0 +1,92 @@
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
href: {
|
||||
type: [] as PropType<any>,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
tight: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
elevated: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
cursor: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
truncate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
classes: 'rounded-md overflow-x-hidden transition-colors'
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="href ? 'SmartLink' : 'div'"
|
||||
:href="href"
|
||||
class="rounded-lg card-base"
|
||||
:class="{
|
||||
[classes]: true,
|
||||
'p-2': tight === true,
|
||||
'p-4': tight === false,
|
||||
'cursor-pointer': cursor === true,
|
||||
'items-center flex space-x-4': $slots.icon || $slots['icon-left'],
|
||||
'justify-between': $slots.icon && !$slots['icon-left']
|
||||
}"
|
||||
v-bind="href ? $attrs : false"
|
||||
>
|
||||
<div v-if="$slots['icon-left']" class="flex-shrink-0">
|
||||
<slot name="icon-left" />
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-hidden leading-relaxed space-y-2">
|
||||
<h2 v-if="title" class="font-medium text-black dark:text-white truncate">
|
||||
{{ title }}
|
||||
</h2>
|
||||
|
||||
<p
|
||||
v-if="$slots.default"
|
||||
class="text-black/50 dark:text-white/30 text-sm"
|
||||
:class="truncate === true && 'line-clamp-2'"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.icon" class="flex-shrink-0">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
76
src/components/Card/LastFm.vue
Executable file
76
src/components/Card/LastFm.vue
Executable file
@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
artist: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
nowPlaying: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
plays: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SmartLink
|
||||
:href="url"
|
||||
:title="name"
|
||||
class="rounded-lg flex items-center gap-4 card-base"
|
||||
:class="{
|
||||
'justify-between': plays !== null
|
||||
}"
|
||||
blank
|
||||
>
|
||||
<div class="flex space-x-4 truncate items-center">
|
||||
<div class="flex-shrink-0 h-14 w-14 relative">
|
||||
<SmartImage :src="image" class="rounded-md" />
|
||||
|
||||
<div
|
||||
v-if="nowPlaying"
|
||||
title="Playing now..."
|
||||
class="rounded-md flex bg-black/75 inset-0 items-center justify-center absolute"
|
||||
>
|
||||
<IconPlay class="h-6 text-neutral-300 w-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col truncate">
|
||||
<span class="truncate">
|
||||
{{ name }}
|
||||
</span>
|
||||
|
||||
<span v-if="artist" class="text-sm text-black/50 dark:text-white/30 truncate">by {{ artist }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="plays"
|
||||
class="rounded-md text-blue-600 bg-blue-600/10 ring-[0.5px] ring-blue-600/40 px-4 py-1 flex-shrink-0 text-xs"
|
||||
>
|
||||
{{ plays }} plays
|
||||
</div>
|
||||
</SmartLink>
|
||||
</template>
|
||||
68
src/components/Card/Post/Index.vue
Executable file
68
src/components/Card/Post/Index.vue
Executable file
@ -0,0 +1,68 @@
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue'
|
||||
|
||||
/* Interfaces */
|
||||
import type { Post } from '@/types/Post'
|
||||
|
||||
export interface PostMeta {
|
||||
title?: string
|
||||
description?: string
|
||||
slug?: string
|
||||
special?: boolean
|
||||
tag?: string
|
||||
image?: string
|
||||
date?: Date
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
post: {
|
||||
type: Object as PropType<Post>,
|
||||
required: true,
|
||||
default: () => {}
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'normal'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hovered: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Returns post meta safely.
|
||||
* @returns {PostMeta |null}
|
||||
*/
|
||||
getPostMeta(): PostMeta {
|
||||
if (!this.post) return {}
|
||||
|
||||
const image = this.post?.image || `/assets/images/posts/${this.post?.slug}.jpg` || ''
|
||||
|
||||
return {
|
||||
title: this.post.title || '',
|
||||
description: this.post.description || '',
|
||||
slug: this.post.slug || '',
|
||||
special: this.post.special || false,
|
||||
tag: this.post?.tags?.[0] || '',
|
||||
date: this.post?.createdAt,
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Normal -->
|
||||
<CardPostNormal v-if="type === 'normal'" :meta="getPostMeta" />
|
||||
|
||||
<!-- Text -->
|
||||
<CardPostText v-else-if="type === 'text'" :meta="getPostMeta" />
|
||||
|
||||
<!-- Text and Title -->
|
||||
<CardPostTextTitle v-else-if="type === 'text-only-title'" :meta="getPostMeta" />
|
||||
</template>
|
||||
54
src/components/Card/Post/Normal.vue
Executable file
54
src/components/Card/Post/Normal.vue
Executable file
@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue'
|
||||
|
||||
// Import type
|
||||
import type { PostMeta } from './Index.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
meta: {
|
||||
type: Object as PropType<PostMeta>,
|
||||
required: true,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hovered: false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="meta" class="overflow-hidden" @mouseover="hovered = true" @mouseleave="hovered = false">
|
||||
<SmartLink
|
||||
:title="meta.title"
|
||||
:href="{
|
||||
name: 'blog-slug',
|
||||
params: { slug: meta.slug }
|
||||
}"
|
||||
class="rounded-lg cursor-pointer space-y-2 focus-ring"
|
||||
>
|
||||
<div class="relative">
|
||||
<SmartImage :src="meta.image" class="rounded h-40 w-full filter dark:brightness-75" />
|
||||
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-show="hovered" class="flex bg-black/50 inset-0 absolute items-center justify-center">
|
||||
<IconLink class="h-6 text-white w-6" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-1">
|
||||
<h2 class="font-medium text-lg leading-tight text-gray-700 truncate dark:text-gray-200 hover:underline">
|
||||
{{ meta.title }}
|
||||
</h2>
|
||||
|
||||
<p class="text-neutral-500 line-clamp-2">
|
||||
{{ meta.description }}
|
||||
</p>
|
||||
</div>
|
||||
</SmartLink>
|
||||
</div>
|
||||
</template>
|
||||
40
src/components/Card/Post/Text.vue
Executable file
40
src/components/Card/Post/Text.vue
Executable file
@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue'
|
||||
|
||||
// Import type
|
||||
import type { PostMeta } from './Index.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
meta: {
|
||||
type: Object as PropType<PostMeta>,
|
||||
required: true,
|
||||
default: () => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SmartLink
|
||||
v-if="meta"
|
||||
:title="meta.title"
|
||||
:href="{
|
||||
name: 'blog-slug',
|
||||
params: { slug: meta.slug }
|
||||
}"
|
||||
class="rounded-lg cursor-pointer flex space-x-4 p-3 transition-colors focus-ring items-center md:px-4 hover:bg-gray-200/40 dark:hover:bg-neutral-800/40"
|
||||
>
|
||||
<SmartImage :src="meta.image" class="rounded flex-shrink-0 h-20 w-24 filter dark:brightness-75" />
|
||||
|
||||
<div class="flex flex-col overflow-x-hidden">
|
||||
<h2 class="font-medium text-lg text-gray-800 truncate dark:text-gray-200">
|
||||
{{ meta.title }}
|
||||
</h2>
|
||||
|
||||
<p class="text-neutral-500 line-clamp-2">
|
||||
{{ meta.description }}
|
||||
</p>
|
||||
</div>
|
||||
</SmartLink>
|
||||
</template>
|
||||
53
src/components/Card/Post/TextTitle.vue
Executable file
53
src/components/Card/Post/TextTitle.vue
Executable file
@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import Vue, { PropType } from 'vue'
|
||||
// Import type
|
||||
import type { PostMeta } from './Index.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
meta: {
|
||||
type: Object as PropType<PostMeta>,
|
||||
required: true,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getPostDate(): string | null {
|
||||
if (!this.meta || !this.meta.date) return null
|
||||
return this.$getReadableDate(this.meta.date)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SmartLink
|
||||
v-if="meta"
|
||||
:title="meta.title"
|
||||
:href="{
|
||||
name: 'blog-slug',
|
||||
params: { slug: meta.slug }
|
||||
}"
|
||||
class="rounded-lg cursor-pointer flex flex-col p-3 px-4 transition-colors focus-ring truncate hover:bg-gray-200/40 dark:hover:bg-neutral-800/40"
|
||||
>
|
||||
<h2 class="font-medium text-lg text-gray-800 truncate dark:text-gray-200">
|
||||
{{ meta.title }}
|
||||
</h2>
|
||||
|
||||
<div class="flex space-x-1 items-center">
|
||||
<IconFire
|
||||
v-if="meta.special"
|
||||
v-tippy="{
|
||||
content: 'Popüler gönderi',
|
||||
placement: 'bottom'
|
||||
}"
|
||||
class="flex-shrink-0 h-5 text-red-600 w-5 dark:text-red-500"
|
||||
/>
|
||||
|
||||
<div class="flex space-x-2 text-gray-700 truncate items-center dark:text-gray-400">
|
||||
<IconClock class="flex-shrink-0 h-5 w-5" />
|
||||
<span class="truncate">{{ getPostDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</SmartLink>
|
||||
</template>
|
||||
84
src/components/Card/Repository.vue
Executable file
84
src/components/Card/Repository.vue
Executable file
@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
stars: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
top: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
license: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Returns proper name for the language icon.
|
||||
* @returns {string}
|
||||
*/
|
||||
getLanguageIcon(): string {
|
||||
const icons = {
|
||||
Vue: 'Vue.js'
|
||||
}
|
||||
|
||||
// @ts-ignore-next-line
|
||||
return icons[this.language] || this.language
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg card-base">
|
||||
<div class="space-y-2">
|
||||
<div :class="top && 'flex justify-between space-x-2'">
|
||||
<h3 class="text-black/90 dark:text-white/90 items-center truncate space-x-1">
|
||||
<span class="text-black/50 dark:text-white/30">@andatoshiki/</span><span>{{ name }}</span>
|
||||
</h3>
|
||||
|
||||
<IconStar v-if="top === true" class="h-6 text-yellow-600 w-6" title="Top repository" filled />
|
||||
</div>
|
||||
|
||||
<p class="text-black/50 dark:text-white/30 line-clamp-2">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="flex items-center justify-between text-black/50 dark:text-white/30">
|
||||
<span>Stars:</span>
|
||||
<span>{{ stars }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-black/50 dark:text-white/30">
|
||||
<span>Language:</span>
|
||||
<IconDev :brand="getLanguageIcon" class="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
<div v-if="license" class="flex items-center justify-between text-black/50 dark:text-white/30">
|
||||
<span>License:</span>
|
||||
<span>{{ license }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
36
src/components/Card/Skill.vue
Executable file
36
src/components/Card/Skill.vue
Executable file
@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
iconPack: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'IconDev'
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-base rounded-lg flex items-center space-x-4">
|
||||
<div class="rounded-lg flex">
|
||||
<SmartImage v-if="image" :src="image" class="h-5 w-5 flex-shrink-0" />
|
||||
<component v-else :is="iconPack" :brand="title" class="flex-shrink-0 h-5 w-5" />
|
||||
</div>
|
||||
|
||||
<span class="flex-1 truncate text-sm">
|
||||
{{ title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
67
src/components/Card/Song.vue
Executable file
67
src/components/Card/Song.vue
Executable file
@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
date: {
|
||||
type: [String, Date],
|
||||
required: true,
|
||||
default: null
|
||||
},
|
||||
thumbnail: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Compares the dates between the provided date and current date and returns a title which will be used in cards' title.
|
||||
* @returns {string} The title "Today's Song" or formatted date.
|
||||
*/
|
||||
getDateText(): string {
|
||||
if (
|
||||
this.$moment(this.date).utcOffset(3).format('DD/MM/YYYY') ===
|
||||
this.$moment(this.$getArizonaTime()).format('DD/MM/YYYY')
|
||||
)
|
||||
return "Today's Song"
|
||||
else return this.$moment(this.date).utcOffset(3).format('DD/MM/YYYY')
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg cursor-pointer card-base flex flex-col space-y-2">
|
||||
<div class="rounded-md flex-shrink-0">
|
||||
<SmartImage
|
||||
:src="thumbnail"
|
||||
fit="outside"
|
||||
class="rounded-md max-w-full max-h-full h-16 w-16"
|
||||
width="64"
|
||||
height="64"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1 truncate">
|
||||
<h3 class="font-medium flex-shrink-0 leading-tight truncate">
|
||||
{{ title }}
|
||||
</h3>
|
||||
|
||||
<div class="flex space-x-1 text-sm items-center text-black/50 dark:text-white/30">
|
||||
<IconStar v-if="getDateText.startsWith('Today')" class="flex-shrink-0 h-4 w-4" />
|
||||
|
||||
<IconCalendar class="h-4 w-4" />
|
||||
|
||||
<span>
|
||||
{{ getDateText }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
33
src/components/Card/Sponsor.vue
Executable file
33
src/components/Card/Sponsor.vue
Executable file
@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
// Types
|
||||
import type { Sponsor } from '~/src/types/Response/Sponsors'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
sponsor: {
|
||||
type: Object as () => Sponsor,
|
||||
required: true
|
||||
},
|
||||
monthly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SmartLink :href="`https://github.com/${sponsor.login}`" class="card-base rounded-lg flex flex-col gap-2" blank>
|
||||
<SmartImage :src="sponsor.avatarUrl" class="h-10 w-10 flex-shrink-0 rounded-full" />
|
||||
|
||||
<div class="flex overflow-x-hidden flex-col leading-tight">
|
||||
<span class="truncate">{{ sponsor.login }}</span>
|
||||
<span class="text-sm text-black/30 dark:text-white/30 truncate">
|
||||
{{ monthly ? 'Monthly' : 'One time' }}
|
||||
</span>
|
||||
</div>
|
||||
</SmartLink>
|
||||
</template>
|
||||
27
src/components/ColorSwitcher.vue
Executable file
27
src/components/ColorSwitcher.vue
Executable file
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
computed: {
|
||||
/**
|
||||
* Returns the selected color mode value.
|
||||
* @returns {string} The color mode as "light" or "dark".
|
||||
*/
|
||||
getSelectedTheme(): string {
|
||||
return this.$colorMode.value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Updates the color mode value.
|
||||
*/
|
||||
switchTheme() {
|
||||
this.$colorMode.preference = this.getSelectedTheme === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button rounded elevated :icon="getSelectedTheme === 'light' ? 'Sun' : 'Moon'" @click.native="switchTheme" />
|
||||
</template>
|
||||
127
src/components/DPlayer.vue
Normal file
127
src/components/DPlayer.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div ref="dplayer" class="rounded-md"></div>
|
||||
</template>
|
||||
<!-- Pitfall record: import DPlayer from 'dplayer' is not allowed, it will cause build error: referenceerror: self is not defined -->
|
||||
<!-- dplayer is asynchronous and needs to be initialized using import("dplayer").then(({ default: DPlayer }) => { }); -->
|
||||
<!-- https://github.com/u2sb/www.u2sb.com/blob/main/docs/OpenSw/vuepress-plugin-smplayer/dplayer.md -->
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// Video URL
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// Subtitle URL
|
||||
zm: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// Autoplay
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// Loop
|
||||
loop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dp: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
import('dplayer').then(({ default: DPlayer }) => {
|
||||
this.dp = new DPlayer({
|
||||
// Player container element
|
||||
container: this.$refs.dplayer,
|
||||
// Enable live mode
|
||||
live: false,
|
||||
// Video autoplay
|
||||
autoplay: this.autoplay,
|
||||
// Theme color
|
||||
theme: 'var(--vp-c-brand-1)',
|
||||
// Video loop
|
||||
loop: this.loop,
|
||||
// Language
|
||||
lang: 'en',
|
||||
// Enable screenshot, if enabled, video and video cover need to allow cross-origin
|
||||
screenshot: false,
|
||||
// Enable hotkeys, support fast forward, rewind, volume control, play/pause
|
||||
hotkey: true,
|
||||
// Enable AirPlay in Safari
|
||||
airplay: true,
|
||||
// Enable Chromecast
|
||||
chromecast: false,
|
||||
// Video preload, optional values: 'none', 'metadata', 'auto'
|
||||
preload: 'auto',
|
||||
// Default volume, please note that the player will remember user settings, and the default volume will be invalid after the user manually sets the volume
|
||||
volume: 0.5,
|
||||
// Optional playback speed, can be set to a custom array
|
||||
playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 2],
|
||||
// Show a logo in the upper left corner, you can adjust its size and position through CSS
|
||||
logo: '',
|
||||
// Prevent automatic toggle of play/pause when clicking on the player
|
||||
preventClickToggle: false,
|
||||
// Video information
|
||||
video: {
|
||||
// Video link
|
||||
url: this.url,
|
||||
// Video cover
|
||||
pic: '/images/dplayer.png',
|
||||
// Optional values: 'auto', 'hls', 'flv', 'dash', 'webtorrent', 'normal' or other custom types
|
||||
type: 'auto'
|
||||
// Quality switching
|
||||
// quality:'',
|
||||
// Default quality
|
||||
// defaultQuality:''
|
||||
// Video thumbnail, can be generated using DPlayer-thumbnails
|
||||
// thumbnails:''
|
||||
// Custom type
|
||||
// customType:''
|
||||
},
|
||||
// Subtitle information
|
||||
subtitle: {
|
||||
// Subtitle link
|
||||
url: this.zm,
|
||||
// Subtitle type, optional values: 'webvtt', 'ass', currently only supports webvtt
|
||||
type: 'webvtt',
|
||||
// Subtitle font size
|
||||
fontSize: '20px',
|
||||
// Distance from the bottom of the player to the subtitle, the value is like: '10px' '10%'
|
||||
bottom: '40px',
|
||||
// Subtitle color
|
||||
color: 'var(--vp-c-brand)'
|
||||
},
|
||||
// Danmaku
|
||||
// https://dplayer.diygod.dev/zh/guide.html#%E5%8F%82%E6%95%B0
|
||||
// Mutual exclusion, prevent multiple players from playing at the same time, pause other players when the current player is playing
|
||||
mutex: true
|
||||
})
|
||||
// Disable right-click to download video
|
||||
this.$refs.dplayer.oncontextmenu = () => {
|
||||
document.querySelector('.dplayer-menu').style.display = 'none'
|
||||
document.querySelector('.dplayer-mask').style.display = 'none'
|
||||
return false
|
||||
}
|
||||
// Modify loop display
|
||||
document
|
||||
.getElementsByClassName('dplayer-setting-item dplayer-setting-loop')[0]
|
||||
.getElementsByClassName('dplayer-label')[0].innerText = 'Loop'
|
||||
// Modify playback speed display
|
||||
document
|
||||
.getElementsByClassName('dplayer-setting-item dplayer-setting-speed')[0]
|
||||
.getElementsByClassName('dplayer-label')[0].innerText = 'Playback Speed'
|
||||
})
|
||||
})
|
||||
},
|
||||
unmounted() {
|
||||
this.dp.destroy()
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
</script>
|
||||
34
src/components/Footer.vue
Executable file
34
src/components/Footer.vue
Executable file
@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
computed: {
|
||||
/**
|
||||
* Returns localized GitHub notice string in Turkish/English according to current route.
|
||||
* @returns {string}
|
||||
*/
|
||||
getLocalizedNotice(): string {
|
||||
if (this.$route.name?.includes('blog'))
|
||||
return 'Made with ❤️ by @andatoshiki at @toshikidev proudly co-founded in the innovative Arizona State University.'
|
||||
else
|
||||
return 'Made with ❤️ by @andatoshiki at @toshikidev proudly co-founded in the innovative Arizona State University.'
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="margin bg-gray-100 text-sm w-full py-4 text-black/50 dark:(bg-white/5 text-white/30)">
|
||||
<div class="responsive-screen">
|
||||
<div class="space-y-4 text-center sm:(space-y-0 space-x-6 text-left)">
|
||||
<SmartLink
|
||||
href="https://github.com/andatoshiki/toshiki-home-nuxt"
|
||||
class="text-center border-b border-transparent hover:border-black/10 dark:hover:border-white/10 transition-colors"
|
||||
blank
|
||||
>
|
||||
{{ getLocalizedNotice }}
|
||||
</SmartLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
54
src/components/GoTop.vue
Executable file
54
src/components/GoTop.vue
Executable file
@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
position: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Checks if the position is higher than a specific number and returns a boolean value.
|
||||
* @returns {boolean} Higher than the given number.
|
||||
*/
|
||||
isActive(): boolean {
|
||||
return this.position > 100
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('scroll', this.updatePosition)
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('scroll', this.updatePosition)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Updates the Vue data when it's called.
|
||||
*/
|
||||
updatePosition() {
|
||||
this.position = window.scrollY
|
||||
},
|
||||
/**
|
||||
* Scrolls window to top.
|
||||
*/
|
||||
goTop() {
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div v-show="isActive" class="right-6 bottom-4 z-50 fixed items-center md:flex md:space-x-2">
|
||||
<Button rounded elevated @click.native="goTop">
|
||||
<template #icon>
|
||||
<IconChevron up class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<ColorSwitcher class="hidden md:block" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
14
src/components/Icon/AcademicHat.vue
Executable file
14
src/components/Icon/AcademicHat.vue
Executable file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path fill="transparent" d="M12 14l9-5-9-5-9 5 9 5z" />
|
||||
<path
|
||||
fill="transparent"
|
||||
d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/At.vue
Executable file
10
src/components/Icon/At.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
11
src/components/Icon/Branch.vue
Executable file
11
src/components/Icon/Branch.vue
Executable file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7 4.5v10M17 9.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM7 19.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM17 9.5A7.5 7.5 0 019.5 17"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.667"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
530
src/components/Icon/Brand.vue
Executable file
530
src/components/Icon/Brand.vue
Executable file
File diff suppressed because one or more lines are too long
10
src/components/Icon/Calendar.vue
Executable file
10
src/components/Icon/Calendar.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
100
src/components/Icon/Channel.vue
Executable file
100
src/components/Icon/Channel.vue
Executable file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<!-- Netflix -->
|
||||
<svg v-if="isSame('Netflix')" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
||||
<path
|
||||
d="M5.398 0v.006c3.028 8.556 5.37 15.175 8.348 23.596 2.344.058 4.85.398 4.854.398-2.8-7.924-5.923-16.747-8.487-24zm8.489 0v9.63L18.6 22.951c-.043-7.86-.004-15.913.002-22.95zM5.398 1.05V24c1.873-.225 2.81-.312 4.715-.398v-9.22z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Disney+ -->
|
||||
<svg v-else-if="isSame('Disney+')" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M20.383 27.7s-1.45.099-2.449.205c-1.27.132-3.657.528-5.034 1.002-.413.142-1.253.48-1.326.889-.076.424.197.755.501 1.098.176.199 1.17 1.12 1.45 1.343 1.169.94 3.54 2.388 5.287 3.092.6.239 1.592.58 1.592.58s-.074-2.719-.06-5.397c.007-1.413.039-2.812.039-2.812zm26.597 1.082c.083.73-.112 2.112-.146 2.294-.062.42-.391 1.389-.446 1.507-.265.588-.527 1.07-.805 1.552-.476.823-1.607 2.13-2.278 2.688-2.497 2.076-6.362 3.259-9.678 3.648-2.25.262-4.835.223-7.22-.201a58.38 58.38 0 01-2.04-.415s.003.47-.036.8a3.23 3.23 0 01-.203.676c-.173.345-.458.523-.872.6-.5.088-1.03.118-1.49-.072-.758-.306-1.03-.989-1.162-1.775-.107-.63-.22-1.723-.22-1.723s-.566-.259-1.039-.486a27.105 27.105 0 01-4.037-2.38 50.303 50.303 0 01-2.087-1.684c-.889-.812-1.688-1.62-2.296-2.656-.473-.81-.61-1.528-.25-2.386.496-1.196 2.278-2.096 3.497-2.609.895-.38 3.678-1.255 4.834-1.416.546-.076 1.393-.222 1.445-.254a.324.324 0 00.052-.046c.026-.036.071-1.22.062-1.653-.01-.425.328-3.221.437-3.813.056-.32.308-1.55.565-1.873.168-.219.465-.201.708-.058 1.326.793 1.728 3.545 1.827 4.945.059.852.088 2.135.088 2.135s1.521-.043 2.457-.017c.91.02 1.911.158 2.855.303 1.208.186 3.563.68 4.914 1.34 1.112.542 2.153 1.456 2.49 2.423.314.887.267 1.5-.21 2.301-.537.904-1.552 1.576-2.581 1.632-.307.017-1.46-.13-1.814-.395-.14-.105-.132-.295-.032-.424.038-.046.577-.321.895-.482.16-.084.291-.174.416-.283.264-.224.502-.47.476-.759-.037-.375-.45-.606-.842-.755-1.845-.705-5.527-1.29-7.307-1.392-.696-.039-1.687-.072-1.687-.072L24.43 37s.819.15 1.464.251c.37.054 1.94.19 2.357.2 3.175.08 6.72-.193 9.633-1.516 1.28-.58 2.453-1.3 3.342-2.277a6.45 6.45 0 001.622-4.907c-.178-2.017-1.653-4.41-2.831-5.869-3.113-3.851-8.448-7.02-13.142-8.877-4.793-1.895-9.53-2.986-14.615-3.168-1.311-.047-4.17.017-5.615.402-.207.056-.415.122-.606.164-.152.035-.39.132-.456.183-.036.028-.072.067-.072.067s.09.048.174.082c.153.064.798.103 1.131.162.298.054.609.204.732.415.117.2.131.357-.008.524-.328.383-1.56.319-2.103.236-.564-.086-1.266-.253-1.395-.725-.15-.556.125-1.102.422-1.606.596-1.01 1.45-1.534 2.701-1.862 1.777-.47 4.02-.8 5.698-.861 3.796-.138 7.39.5 11.069 1.575 2.105.613 4.862 1.64 6.88 2.576 1.448.671 3.73 1.907 5.011 2.714.404.257 2.77 1.93 3.137 2.223a38.43 38.43 0 012.495 2.163c1.405 1.34 3.152 3.392 4 5.022.205.39.363.774.627 1.226.09.155.478 1.081.543 1.35.063.264.157.653.17.669.019.142.199.938.185 1.245z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Amazon Prime -->
|
||||
<svg v-else-if="isSame('Amazon Prime')" viewbox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
<g fill-rule="evenodd">
|
||||
<path
|
||||
d="M14.506 12.63c-1.758 1.296-4.305 1.986-6.498 1.986-3.081 0-5.845-1.137-7.94-3.03-.159-.148-.016-.351.181-.235 2.26 1.316 5.06 2.107 7.943 2.107 2.082-.01 4.141-.43 6.06-1.239.297-.126.547.195.254.41zm.731-.837c-.223-.287-1.485-.135-2.05-.069-.173.021-.199-.129-.044-.237 1.01-.706 2.653-.503 2.846-.265.192.237-.05 1.89-.994 2.68-.145.12-.283.056-.212-.105.212-.529.687-1.716.462-2.004M9.585 6.75c0 .579.015 1.062-.291 1.577-.251.417-.645.675-1.077.675-.598 0-.948-.435-.948-1.077 0-1.267 1.189-1.498 2.316-1.498v.322zm1.57 3.625a.337.337 0 0 1-.368.035c-.516-.41-.61-.6-.893-.991-.854.832-1.46 1.081-2.565 1.081C6.018 10.5 5 9.728 5 8.182c0-1.207.683-2.03 1.66-2.431.844-.356 2.024-.418 2.925-.514V5.05c0-.354.03-.772-.19-1.078-.188-.274-.551-.387-.873-.387-.593 0-1.12.291-1.25.894-.026.137-.129.265-.27.272l-1.514-.162c-.126-.027-.268-.125-.231-.308C5.606 2.528 7.26 2 8.74 2c.758 0 1.747.193 2.344.74.758.676.685 1.578.685 2.56v2.318c0 .697.303 1.002.587 1.379.099.137.12.294-.006.395-.402.324-.8.654-1.193.987l-.002-.004"
|
||||
class="dark:fill-white"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- Apple TV+ -->
|
||||
<svg
|
||||
v-else-if="isSame('Apple TV+')"
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 1024 1024"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M747.4 535.7c-.4-68.2 30.5-119.6 92.9-157.5-34.9-50-87.7-77.5-157.3-82.8-65.9-5.2-138 38.4-164.4 38.4-27.9 0-91.7-36.6-141.9-36.6C273.1 298.8 163 379.8 163 544.6c0 48.7 8.9 99 26.7 150.8 23.8 68.2 109.6 235.3 199.1 232.6 46.8-1.1 79.9-33.2 140.8-33.2 59.1 0 89.7 33.2 141.9 33.2 90.3-1.3 167.9-153.2 190.5-221.6-121.1-57.1-114.6-167.2-114.6-170.7zm-105.1-305c50.7-60.2 46.1-115 44.6-134.7-44.8 2.6-96.6 30.5-126.1 64.8-32.5 36.8-51.6 82.3-47.5 133.6 48.4 3.7 92.6-21.2 129-63.7z"
|
||||
stroke="none"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- FOX -->
|
||||
<svg v-else-if="isSame('FOX')" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.901 2c-.005 0-.005.01-.005.018-.01 6.646-.01 13.333.003 19.977.007 0 .007.007.017.005h5.524c.003-.003.005-.006.005-.013 0-2.164-.01-4.287-.013-6.451 1.806-.013 3.636 0 5.452-.005v-5.456c-1.803-.002-3.621.01-5.411-.005.007-.841-.016-1.766.012-2.602h6.626c-.12-1.822-.24-3.644-.364-5.463C17.744 2 17.742 2 17.734 2H5.901z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Adult Swim -->
|
||||
<svg v-else-if="isSame('Adult Swim')" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M22.318 19.124c.042-1.953.553-3.083 2.328-3.083 1.424 0 2.008.885 2.008 2.25 0 1.718-.353 2.046-2.28 2.587-1.776.501-4.14.916-5.722 1.84-1.575.917-2.602 3.124-2.602 5.766 0 4.872 2.091 6.88 5.678 6.88 2.134 0 4.141-1.168 5.01-3.173h.086c.024.879.11 1.8.346 2.587h6.148c-.743-.997-.743-3.176-.743-4.881V18.29c0-4.753-2.328-6.67-7.576-6.67-2.639 0-4.456.372-5.946 1.451-1.502 1.087-2.365 2.97-2.408 6.053h5.673zm-.115 8.688c0-1.259.316-1.97.985-2.5.59-.503 1.459-.547 3.466-1.677 0 .917-.03 2.095-.03 3.968 0 2.246-1.23 3.088-2.566 3.088-1.186 0-1.855-1.083-1.855-2.879zM2 6h9.977v4.675H7.91V38.8h4.067v4.669H2V6zM37.652 39.065h4.062V10.94h-4.062V6.266h9.966v37.468h-9.966v-4.67z"
|
||||
class="dark:fill-white"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- BBC -->
|
||||
<svg v-else-if="isSame('BBC')" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M31.12 24.43s4.274-1.853 4.236-6.77c0 0 .65-8.058-9.84-9.04H13.879V42.4h13.341s11.146.034 11.146-9.532c0 0 .263-6.509-7.245-8.438zm-5.09 12.75s6.244.303 6.244-4.804c0 0 .183-4.469-6.244-4.425h-6.244v9.229h6.244zm-1.485-23.303h-4.759v8.739h4.05s5.463-.076 5.463-4.73c0 0 .187-3.743-4.754-4.01z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- TNT -->
|
||||
<svg v-else-if="isSame('TNT')" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M41 14.271H29.642v29.567h-9.926V14.27H8.344V5.732H41v8.54z" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
platform: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'Netflix'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Checks if passed value matches with the prop value.
|
||||
* @prop {string} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSame(value: string): boolean {
|
||||
return this.platform?.toLowerCase() === value?.toLowerCase()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
5
src/components/Icon/Check.vue
Executable file
5
src/components/Icon/Check.vue
Executable file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</template>
|
||||
84
src/components/Icon/Chevron.vue
Executable file
84
src/components/Icon/Chevron.vue
Executable file
@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<!-- Up -->
|
||||
<svg v-if="up === true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
|
||||
<!-- Down -->
|
||||
<svg
|
||||
v-else-if="down === true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
|
||||
<!-- Right -->
|
||||
<svg
|
||||
v-else-if="right === true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
|
||||
<!-- Left -->
|
||||
<svg
|
||||
v-else-if="left === true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
|
||||
<!-- Double Left -->
|
||||
<svg
|
||||
v-else-if="doubleLeft === true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
up: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
down: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
right: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
left: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
doubleLeft: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
10
src/components/Icon/Clock.vue
Executable file
10
src/components/Icon/Clock.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
11
src/components/Icon/Cog.vue
Executable file
11
src/components/Icon/Cog.vue
Executable file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</template>
|
||||
522
src/components/Icon/Dev.vue
Executable file
522
src/components/Icon/Dev.vue
Executable file
File diff suppressed because one or more lines are too long
9
src/components/Icon/Document.vue
Executable file
9
src/components/Icon/Document.vue
Executable file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
9
src/components/Icon/Dollar.vue
Executable file
9
src/components/Icon/Dollar.vue
Executable file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
9
src/components/Icon/Ellipsis.vue
Executable file
9
src/components/Icon/Ellipsis.vue
Executable file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Exclamation.vue
Executable file
10
src/components/Icon/Exclamation.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Eye.vue
Executable file
10
src/components/Icon/Eye.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
|
||||
/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</template>
|
||||
9
src/components/Icon/Fire.vue
Executable file
9
src/components/Icon/Fire.vue
Executable file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-4 1.25-4.5.5 1 .786 1.293 1.371 1.879A2.99 2.99 0 0113 13a2.99 2.99 0 01-.879 2.121z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Fork.vue
Executable file
10
src/components/Icon/Fork.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7.833 12h6.25a2.917 2.917 0 002.892-2.533 2.916 2.916 0 11.838.006 3.75 3.75 0 01-3.73 3.36h-6.25v1.696a2.917 2.917 0 11-.833 0V9.471a2.917 2.917 0 11.833 0V12zm-2.5 5.417a2.083 2.083 0 104.167 0 2.083 2.083 0 00-4.167 0v0zm0-10.834a2.083 2.083 0 104.167 0 2.083 2.083 0 00-4.167 0v0zM17.417 4.5a2.083 2.083 0 100 4.167 2.083 2.083 0 000-4.167z"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
stroke-width=".833"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Home.vue
Executable file
10
src/components/Icon/Home.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
5
src/components/Icon/IconBack.vue
Executable file
5
src/components/Icon/IconBack.vue
Executable file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" />
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Inbox.vue
Executable file
10
src/components/Icon/Inbox.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 4H6a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-2m-4-1v8m0 0l3-3m-3 3L9 8m-5 5h2.586a1 1 0 01.707.293l2.414 2.414a1 1 0 00.707.293h3.172a1 1 0 00.707-.293l2.414-2.414a1 1 0 01.707-.293H20"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Link.vue
Executable file
10
src/components/Icon/Link.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
5
src/components/Icon/Menu.vue
Executable file
5
src/components/Icon/Menu.vue
Executable file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Moon.vue
Executable file
10
src/components/Icon/Moon.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
8
src/components/Icon/MusicNote.vue
Executable file
8
src/components/Icon/MusicNote.vue
Executable file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10 21q-1.65 0-2.825-1.175Q6 18.65 6 17q0-1.65 1.175-2.825Q8.35 13 10 13q.575 0 1.062.137q.488.138.938.413V3h6v4h-4v10q0 1.65-1.175 2.825Q11.65 21 10 21Z"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
11
src/components/Icon/Play.vue
Executable file
11
src/components/Icon/Play.vue
Executable file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M17.886 9.874L9.89 4.429a2.46 2.46 0 00-2.57-.126c-.4.219-.734.544-.966.942A2.594 2.594 0 006 6.559v10.887c0 .462.123.916.356 1.313.232.396.566.72.965.939a2.46 2.46 0 002.569-.127l7.996-5.445c.343-.233.624-.55.818-.92a2.597 2.597 0 000-2.41 2.536 2.536 0 00-.818-.92v-.002 0z"
|
||||
fill="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
5
src/components/Icon/Plus.vue
Executable file
5
src/components/Icon/Plus.vue
Executable file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Question.vue
Executable file
10
src/components/Icon/Question.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
10
src/components/Icon/Search.vue
Executable file
10
src/components/Icon/Search.vue
Executable file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
36
src/components/Icon/Star.vue
Executable file
36
src/components/Icon/Star.vue
Executable file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<svg
|
||||
v-if="filled === false"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
filled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user