From c3696d25ef36a493100bda861216bc47f6f14f38 Mon Sep 17 00:00:00 2001 From: ULIVZ <472590061@qq.com> Date: Sun, 3 Jun 2018 04:38:33 +0800 Subject: [PATCH] feat: headers badge (#540) --- docs/guide/markdown.md | 2 +- lib/app/app.js | 2 ++ lib/default-theme/Badge.vue | 30 +++++++++++++++++++++++++++++ lib/default-theme/styles/theme.styl | 1 + lib/default-theme/styles/toc.styl | 3 +++ lib/markdown/index.js | 5 +++-- lib/util/index.js | 10 +++++----- lib/util/parseHeaders.js | 24 +++++++++++++++-------- lib/util/shared.js | 7 +++++++ 9 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 lib/default-theme/Badge.vue create mode 100644 lib/default-theme/styles/toc.styl create mode 100644 lib/util/shared.js diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index 5f9548e15f..71d03c7dc4 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -224,7 +224,7 @@ export default { } ``` -## Import Code Snippets +## Import Code Snippets You can import code snippets from existing files via following syntax: diff --git a/lib/app/app.js b/lib/app/app.js index d8da7dccc0..1f64553e8a 100644 --- a/lib/app/app.js +++ b/lib/app/app.js @@ -2,6 +2,7 @@ import Vue from 'vue' import Router from 'vue-router' import Content from './Content' import OutboundLink from '../default-theme/OutboundLink.vue' +import Badge from '../default-theme/Badge.vue' import ClientOnly from './ClientOnly' import dataMixin from './dataMixin' import store from './store' @@ -30,6 +31,7 @@ Vue.mixin(dataMixin(siteData)) // component for rendering markdown content and setting title etc. Vue.component('Content', Content) Vue.component('OutboundLink', OutboundLink) +Vue.component('Badge', Badge) // component for client-only content Vue.component('ClientOnly', ClientOnly) diff --git a/lib/default-theme/Badge.vue b/lib/default-theme/Badge.vue new file mode 100644 index 0000000000..1a41d7e772 --- /dev/null +++ b/lib/default-theme/Badge.vue @@ -0,0 +1,30 @@ + + + diff --git a/lib/default-theme/styles/theme.styl b/lib/default-theme/styles/theme.styl index a8c513cd5f..a733861fa6 100644 --- a/lib/default-theme/styles/theme.styl +++ b/lib/default-theme/styles/theme.styl @@ -4,6 +4,7 @@ @require './custom-blocks' @require './arrow' @require './wrapper' +@require './toc' html, body padding 0 diff --git a/lib/default-theme/styles/toc.styl b/lib/default-theme/styles/toc.styl new file mode 100644 index 0000000000..d3e71069ba --- /dev/null +++ b/lib/default-theme/styles/toc.styl @@ -0,0 +1,3 @@ +.table-of-contents + .badge + vertical-align middle diff --git a/lib/markdown/index.js b/lib/markdown/index.js index 370cee617f..06779ac5ef 100644 --- a/lib/markdown/index.js +++ b/lib/markdown/index.js @@ -11,11 +11,12 @@ const emoji = require('markdown-it-emoji') const anchor = require('markdown-it-anchor') const toc = require('markdown-it-table-of-contents') const _slugify = require('./slugify') -const parseHeaders = require('../util/parseHeaders') +const { parseHeaders, removeTailHtml } = require('../util/parseHeaders') +const { compose } = require('../util/shared') module.exports = ({ markdown = {}} = {}) => { // allow user config slugify - const slugify = markdown.slugify || _slugify + const slugify = markdown.slugify || compose(removeTailHtml, _slugify) const md = require('markdown-it')({ html: true, diff --git a/lib/util/index.js b/lib/util/index.js index 12caf0e7ac..8918ee3e6b 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -1,4 +1,4 @@ -const parseHeaders = require('./parseHeaders') +const { deeplyParseHeaders } = require('./parseHeaders') exports.normalizeHeadTag = function (tag) { if (typeof tag === 'string') { @@ -32,11 +32,11 @@ exports.inferTitle = function (frontmatter) { return 'Home' } if (frontmatter.data.title) { - return parseHeaders(frontmatter.data.title) + return deeplyParseHeaders(frontmatter.data.title) } const match = frontmatter.content.trim().match(/^#+\s+(.*)/) if (match) { - return parseHeaders(match[1]) + return deeplyParseHeaders(match[1]) } } @@ -68,11 +68,11 @@ exports.extractHeaders = function (content, include = [], md) { const res = [] tokens.forEach((t, i) => { if (t.type === 'heading_open' && include.includes(t.tag)) { - const title = parseHeaders(tokens[i + 1].content) + const title = tokens[i + 1].content const slug = t.attrs.find(([name]) => name === 'id')[1] res.push({ level: parseInt(t.tag.slice(1), 10), - title, + title: deeplyParseHeaders(title), slug: slug || md.slugify(title) }) } diff --git a/lib/util/parseHeaders.js b/lib/util/parseHeaders.js index 2a2c4fee4c..3837eeba39 100644 --- a/lib/util/parseHeaders.js +++ b/lib/util/parseHeaders.js @@ -1,3 +1,5 @@ +const { compose } = require('./shared') + const parseEmojis = str => { const emojiData = require('markdown-it-emoji/lib/data/full.json') return String(str).replace(/:(.+?):/g, (placeholder, key) => emojiData[key] || placeholder) @@ -17,13 +19,19 @@ const removeMarkdownToken = str => String(str) .replace(/\*(.*)\*/, '$1') // * .replace(/_(.*)_/, '$1') // _ -// put here to avoid circular references -const compose = (...processors) => { - if (processors.length === 0) return input => input - if (processors.length === 1) return processors[0] - return processors.reduce((prev, next) => { - return (...args) => next(prev(...args)) - }) +exports.removeTailHtml = (str) => { + return String(str).replace(/<.*>\s*$/g, '') } -module.exports = compose(unescapeHtml, parseEmojis, removeMarkdownToken) +// only remove some md tokens. +exports.parseHeaders = compose( + unescapeHtml, + parseEmojis, + removeMarkdownToken +) + +// also clean html in headers. +exports.deeplyParseHeaders = compose( + exports.parseHeaders, + exports.removeTailHtml +) diff --git a/lib/util/shared.js b/lib/util/shared.js new file mode 100644 index 0000000000..023f63bd1b --- /dev/null +++ b/lib/util/shared.js @@ -0,0 +1,7 @@ +exports.compose = (...processors) => { + if (processors.length === 0) return input => input + if (processors.length === 1) return processors[0] + return processors.reduce((prev, next) => { + return (...args) => next(prev(...args)) + }) +}