vuepress-theme-blog-vuetify
Version:
The world's most simple, beautiful and customizable 2 columns VuePress blog theme built with Vuetify
409 lines (350 loc) • 10.8 kB
JavaScript
const Path = require('path')
const Fs = require('fs')
const removeMd = require('remove-markdown')
const pick = require('lodash/pick')
const defaultsDeep = require('lodash/defaultsDeep')
module.exports = (option, ctx) => {
let { themeConfig, siteConfig, sourceDir, writeTemp } = ctx
/**
* Default theme configuration
*/
themeConfig = defaultsDeep(themeConfig, {
// @see https://vuepress-plugin-blog.ulivz.com/guide/getting-started.html#directory-classifier
// @see https://vuepress-theme-blog.ulivz.com/config/#directories
directories: [
{
id: 'post',
dirname: '_posts',
path: '/',
title: '',
frontmatter: {
home: true,
},
},
],
// @see https://vuepress-plugin-blog.ulivz.com/guide/getting-started.html#frontmatter-classifier
// @see https://vuepress-theme-blog.ulivz.com/config/#frontmatters
frontmatters: [
// id: 'tag' and id: 'author' are required
{
id: 'tag',
keys: ['tags'],
path: '/tag/',
},
{
id: 'author',
keys: ['author'],
path: '/author/',
},
],
// @see https://vuepress-plugin-blog.ulivz.com/guide/getting-started.html#pagination
// @see https://vuepress-plugin-blog.ulivz.com/pagination/
// @see https://vuepress-theme-blog.ulivz.com/config/#globalpagination
globalPagination: {
sorter: (prev, next) => {
const dayjs = require('dayjs')
const prevTime = prev.frontmatter.date ? dayjs(prev.frontmatter.date) : dayjs(0)
const nextTime = next.frontmatter.date ? dayjs(next.frontmatter.date) : dayjs(0)
return nextTime - prevTime > 0 ? 1 : -1
},
lengthPerPage: 10,
},
titleIcon: null,
titleHtml: `<strong>${siteConfig.title}</strong>`,
sidebar: {
directoryIds: ['post'],
profile: {
avatarUrl: '',
name: '',
subTitle: '',
descriptionHtml: '',
},
hotTags: 10,
recentPosts: 5,
additionalBlocks: [
// {
// title: 'Pages',
// links: [
// { label: 'About', path: '/about' },
// { label: 'Contact', path: '/contact' },
// { label: 'External Page', url: 'https://example.com'}
// ],
// },
],
},
footer: {
links: [
// { label: 'About', path: '/about' },
// { label: 'Contact', path: '/contact' },
// { label: 'External Page', url: 'https://example.com'}
],
},
sns: {
twitter: '',
facebook: '',
instagram: '',
github: '',
youtube: '',
feed: '/rss.xml',
order: [
'twitter',
'facebook',
'instagram',
'github',
'youtube',
'feed',
],
},
seo: {
baseUrl: '',
author: '',
image: '',
fbAdmins: '',
fbAppId: '',
twitterSite: '',
twitterCreator: '',
articleDirectoryNames: [
'_posts',
],
},
sitemap: true,
feed: {
rss: true,
atom: false,
json: false,
},
ga: '',
showThumbnail: true,
summary: 200,
dateFormat: 'YYYY-MM-DD',
components: {
pagination: 'VuetifyPagination',
beforeSidebar: 'DefaultBeforeSidebar',
afterSidebar: 'DefaultAfterSidebar',
beforeSticky: 'DefaultBeforeSticky',
afterSticky: 'DefaultAfterSticky',
beforePageList: 'DefaultBeforePageList',
beforePagination: 'DefaultBeforePagination',
afterPageList: 'DefaultAfterPageList',
beforePage: 'DefaultBeforePage',
beforePageSection: 'DefaultBeforePageSection',
afterPage: 'DefaultAfterPage',
afterFooter: 'DefaultAfterFooter',
},
})
// this enables to access to global computed $themeConfig
// @see https://vuepress.vuejs.org/guide/global-computed.html#themeconfig
siteConfig.themeConfig = themeConfig
/**
* Configure vuepress-plugin-sitemap installed via @vuepress/plugin-blog
*
* @see https://vuepress-plugin-blog.ulivz.com/guide/getting-started.html#sitemap
* @see https://vuepress-theme-blog.ulivz.com/config/#sitemap
*/
const sitemapPluginOptions = themeConfig.sitemap ? {
hostname: themeConfig.seo.baseUrl,
} : {}
/**
* Configure vuepress-plugin-feed installed via @vuepress/plugin-blog
*
* @see https://vuepress-plugin-blog.ulivz.com/guide/getting-started.html#feed
* @see https://vuepress-theme-blog.ulivz.com/config/#feed
*/
let resolvedFeedOptions
if (themeConfig.feed.rss || themeConfig.feed.atom || themeConfig.feed.json) {
const {
rss,
atom,
json,
...feedOptions
} = themeConfig.feed
resolvedFeedOptions = Object.assign({}, feedOptions, {
canonical_base: themeConfig.seo.baseUrl,
feeds: {
rss2: {enable: rss},
atom1: {enable: atom},
json1: {enable: json},
},
})
} else {
resolvedFeedOptions = {}
}
/**
* Configure @vuepress/plugin-blog
*/
let defaultBlogPluginOptions = {
directories: themeConfig.directories,
frontmatters: themeConfig.frontmatters,
globalPagination: themeConfig.globalPagination,
sitemap: sitemapPluginOptions,
feed: resolvedFeedOptions,
}
/**
* Finish configuring @vuepress/plugin-blog
*/
const blogPluginRelatedProperties = [
'directories',
'frontmatters',
'globalPagination',
]
const themeConfigPluginOptions = {
...pick(themeConfig, blogPluginRelatedProperties),
}
const blogPluginOptions = defaultsDeep(themeConfigPluginOptions, defaultBlogPluginOptions)
/**
* Configure @vuepress/plugin-search
*
* @see https://vuepress.vuejs.org/plugin/official/plugin-search.html
*/
const searchPluginOptions = {
searchMaxSuggestions: themeConfig.searchMaxSuggestions || 10,
test: themeConfig.test || null,
searchHotkeys: themeConfig.searchHotkeys || ['s', '/'],
}
/**
* Configure vuepress-plugin-seo
*
* @see https://github.com/lorisleiva/vuepress-plugin-seo
*/
const seoPluginOptions = {
siteTitle: (_, $site) => $site.title,
title: $page => $page.title,
description: $page => $page.frontmatter.description,
author: (_, $site) => $site.themeConfig.seo.author,
tags: $page => $page.frontmatter.tags,
twitterCard: _ => 'summary_large_image',
type: ($page, $site) => $site.themeConfig.seo.articleDirectoryNames.some(directoryName => $page.regularPath.startsWith('/' + directoryName)) ? 'article' : 'website',
url: (_, $site, path) => $site.themeConfig.seo.baseUrl + path,
image: ($page, $site) => $page.frontmatter.image || $site.themeConfig.seo.image,
publishedAt: $page => {
const dayjs = require('dayjs')
return $page.frontmatter.date && dayjs($page.frontmatter.date)
},
modifiedAt: $page => {
const dayjs = require('dayjs')
return $page.frontmatter.update && dayjs($page.frontmatter.update)
},
customMeta: (add, context) => {
const {
$site, $page, siteTitle, title, description, author, tags, twitterCard, type, url, image, publishedAt, modifiedAt,
} = context
if ($site.themeConfig.seo.fbAdmins) {
add('fb:admins', $site.themeConfig.seo.fbAdmins, 'property')
}
if ($site.themeConfig.seo.fbAppId) {
add('fb:app_id', $site.themeConfig.seo.fbAppId, 'property')
}
if ($site.themeConfig.seo.twitterSite) {
add('twitter:site', $site.themeConfig.seo.twitterSite)
}
if ($site.themeConfig.seo.twitterCreator) {
add('twitter:creator', $site.themeConfig.seo.twitterCreator)
}
},
}
/**
* Integrate plugins
*/
let plugins = [
['@vuepress/blog', blogPluginOptions],
'@vuepress/nprogress',
['@vuepress/medium-zoom', {
selector: '#content article section :not(a) > img',
}],
['@vuepress/search', searchPluginOptions],
['seo', seoPluginOptions],
['container', {type: 'tip'}],
['container', {type: 'warning'}],
['container', {type: 'danger'}],
['container', {
type: 'details',
before: info => `<details class="custom-block details">${info ? `<summary>${info}</summary>` : ''}\n`,
after: () => '</details>\n'
}],
]
if (themeConfig.ga) {
plugins.push(['@vuepress/google-analytics', {
ga: themeConfig.ga,
}])
}
/**
* Apply user variables.scss
*/
validUserVariablesScssFileNames = ['variables.scss', '_variables.scss', 'palette.scss']
writeTemp('_variables.scss', '')
for (fileName of validUserVariablesScssFileNames) {
userVariablesScssFilePath = Path.resolve(sourceDir, `.vuepress/styles/${fileName}`)
if (Fs.existsSync(userVariablesScssFilePath)) {
writeTemp('_variables.scss', Fs.readFileSync(userVariablesScssFilePath))
break
}
}
/**
* Extend page data
*
* @see https://vuepress.vuejs.org/plugin/option-api.html#extendpagedata
*/
const pageDataExtender = page => {
/**
* Generate page summary
*/
if (themeConfig.summary > 0) {
let content = page._strippedContent
if (!content) {
return
}
if (page.excerpt) {
content = content.replace(/([^]*)<!-- *more *-->[^]*/ig, "$1")
}
let summary = removeMd(content
.trim()
.replace(/:::.*(\n|\r|$)/g, '') // remove custom container sign
.replace(/\[\[toc\]\]/ig, '') // remove [[toc]]
.replace(/(\|\s*:?-+:?\s*)+\|/g, '') // remove table border
).replace(/\s+/g, ' ')
if (page.excerpt) {
page.summary = summary + ' ...'
} else {
const postfix = summary.length > themeConfig.summary ? ' ...' : ''
page.summary = summary.slice(0, themeConfig.summary) + postfix
}
page.frontmatter.description = page.frontmatter.summary || page.summary
}
/**
* Resolve tags
*/
let tags = page.frontmatter.tags
if (!tags) {
page.frontmatter.tags = []
} else if (!Array.isArray(tags)) {
page.frontmatter.tags = tags.split(/ +/)
}
/**
* Reduce toc header levels
*/
if (page.headers) {
let minimumLevel = 6
page.headers.forEach(header => {
if (header.level < minimumLevel) {
minimumLevel = header.level
}
})
page.headers.forEach(header => {
header.reducedLevel = header.level - minimumLevel
})
}
/**
* Render some html tags in page title
*/
page.title = page.frontmatter.title && page.frontmatter.title.replace(/<([^>]*)>/g, "<$1>")
}
/**
* Finish configuration
*
* @see https://vuepress.vuejs.org/config/
*/
return {
plugins,
extendPageData: pageDataExtender,
}
}