feat: publicize doc implemetation

This commit is contained in:
Justineo
2020-08-13 11:47:56 +08:00
parent 55b9b044f2
commit 1e5fcff6ad
372 changed files with 50636 additions and 0 deletions

226
one/build/customBlock.js Normal file
View File

@@ -0,0 +1,226 @@
'use strict'
var trim = require('trim-trailing-lines')
module.exports = customBlock
var C_NEWLINE = '\n'
var C_TAB = '\t'
var C_SPACE = ' '
var C_COLON = ':'
var MIN_FENCE_COUNT = 3
var CODE_INDENT_COUNT = 4
function customBlock (eat, value, silent) {
var self = this
var length = value.length + 1
var index = 0
var subvalue = ''
var fenceCount
var marker
var character
var flag
var queue
var content
var exdentedContent
var closing
var exdentedClosing
var indent
var now
/* Eat initial spacing. */
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
subvalue += character
index++
}
indent = index
/* Eat the fence. */
character = value.charAt(index)
if (character !== C_COLON) {
return
}
index++
marker = character
fenceCount = 1
subvalue += character
while (index < length) {
character = value.charAt(index)
if (character !== marker) {
break
}
subvalue += character
fenceCount++
index++
}
if (fenceCount < MIN_FENCE_COUNT) {
return
}
/* Eat spacing before flag. */
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
subvalue += character
index++
}
/* Eat flag. */
flag = ''
queue = ''
while (index < length) {
character = value.charAt(index)
if (character === C_NEWLINE || character === C_COLON) {
break
}
if (character === C_SPACE || character === C_TAB) {
queue += character
} else {
flag += queue + character
queue = ''
}
index++
}
character = value.charAt(index)
if (character && character !== C_NEWLINE) {
return
}
if (silent) {
return true
}
now = eat.now()
now.column += subvalue.length
now.offset += subvalue.length
subvalue += flag
flag = self.decode.raw(self.unescape(flag), now)
if (queue) {
subvalue += queue
}
queue = ''
closing = ''
exdentedClosing = ''
content = ''
exdentedContent = ''
/* Eat content. */
while (index < length) {
character = value.charAt(index)
content += closing
exdentedContent += exdentedClosing
closing = ''
exdentedClosing = ''
if (character !== C_NEWLINE) {
content += character
exdentedClosing += character
index++
continue
}
/* Add the newline to `subvalue` if its the first
* character. Otherwise, add it to the `closing`
* queue. */
if (content) {
closing += character
exdentedClosing += character
} else {
subvalue += character
}
queue = ''
index++
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE) {
break
}
queue += character
index++
}
closing += queue
exdentedClosing += queue.slice(indent)
if (queue.length >= CODE_INDENT_COUNT) {
continue
}
queue = ''
while (index < length) {
character = value.charAt(index)
if (character !== marker) {
break
}
queue += character
index++
}
closing += queue
exdentedClosing += queue
if (queue.length < fenceCount) {
continue
}
queue = ''
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
closing += character
exdentedClosing += character
index++
}
if (!character || character === C_NEWLINE) {
break
}
}
subvalue += content + closing
return eat(subvalue)({
type: 'customblock',
className: (flag || '').trim().replace(/\s+/g, ' '),
value: trim(exdentedContent)
})
}

48
one/build/deps.js Normal file
View File

@@ -0,0 +1,48 @@
import path from 'path'
import { readFileSync, writeFileSync } from './util'
import { merge } from 'lodash'
function load () {
try {
return JSON.parse(readFileSync(resolve('./deps.json')))
} catch (e) {
return {}
}
}
function save (deps) {
writeFileSync(resolve('./deps.json'), JSON.stringify(deps, null, ' '))
}
function resolve (file) {
return path.resolve(__dirname, file)
}
export function get (file) {
let deps = load()
return deps[file]
}
export function add (data) {
let deps = load()
save(merge(deps, data))
}
export function remove (data) {
let deps = load()
Object.keys(data).forEach(key => {
if (deps[key]) {
delete deps[key][data[key]]
}
})
save(deps)
}
export function removeFile (file) {
let deps = load()
delete deps[file]
Object.keys(deps).forEach(key => {
delete deps[key][file]
})
}

226
one/build/details.js Normal file
View File

@@ -0,0 +1,226 @@
'use strict'
var trim = require('trim-trailing-lines')
module.exports = details
var C_NEWLINE = '\n'
var C_TAB = '\t'
var C_SPACE = ' '
var C_PLUS = '+'
var MIN_FENCE_COUNT = 3
var CODE_INDENT_COUNT = 4
function details (eat, value, silent) {
var self = this
var length = value.length + 1
var index = 0
var subvalue = ''
var fenceCount
var marker
var character
var flag
var queue
var content
var exdentedContent
var closing
var exdentedClosing
var indent
var now
/* Eat initial spacing. */
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
subvalue += character
index++
}
indent = index
/* Eat the fence. */
character = value.charAt(index)
if (character !== C_PLUS) {
return
}
index++
marker = character
fenceCount = 1
subvalue += character
while (index < length) {
character = value.charAt(index)
if (character !== marker) {
break
}
subvalue += character
fenceCount++
index++
}
if (fenceCount < MIN_FENCE_COUNT) {
return
}
/* Eat spacing before flag. */
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
subvalue += character
index++
}
/* Eat flag. */
flag = ''
queue = ''
while (index < length) {
character = value.charAt(index)
if (character === C_NEWLINE || character === C_PLUS) {
break
}
if (character === C_SPACE || character === C_TAB) {
queue += character
} else {
flag += queue + character
queue = ''
}
index++
}
character = value.charAt(index)
if (character && character !== C_NEWLINE) {
return
}
if (silent) {
return true
}
now = eat.now()
now.column += subvalue.length
now.offset += subvalue.length
subvalue += flag
flag = self.decode.raw(self.unescape(flag), now)
if (queue) {
subvalue += queue
}
queue = ''
closing = ''
exdentedClosing = ''
content = ''
exdentedContent = ''
/* Eat content. */
while (index < length) {
character = value.charAt(index)
content += closing
exdentedContent += exdentedClosing
closing = ''
exdentedClosing = ''
if (character !== C_NEWLINE) {
content += character
exdentedClosing += character
index++
continue
}
/* Add the newline to `subvalue` if its the first
* character. Otherwise, add it to the `closing`
* queue. */
if (content) {
closing += character
exdentedClosing += character
} else {
subvalue += character
}
queue = ''
index++
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE) {
break
}
queue += character
index++
}
closing += queue
exdentedClosing += queue.slice(indent)
if (queue.length >= CODE_INDENT_COUNT) {
continue
}
queue = ''
while (index < length) {
character = value.charAt(index)
if (character !== marker) {
break
}
queue += character
index++
}
closing += queue
exdentedClosing += queue
if (queue.length < fenceCount) {
continue
}
queue = ''
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
closing += character
exdentedClosing += character
index++
}
if (!character || character === C_NEWLINE) {
break
}
}
subvalue += content + closing
return eat(subvalue)({
type: 'details',
summary: flag || null,
value: trim(exdentedContent)
})
}

3
one/build/generate.js Normal file
View File

@@ -0,0 +1,3 @@
import { generatePages } from './generator'
generatePages()

105
one/build/generator.js Normal file
View File

@@ -0,0 +1,105 @@
import { statSync } from 'fs'
import { resolve, relative, extname, basename, sep } from 'path'
import readdirpSync from 'recursive-readdir-sync'
import rimraf from 'rimraf'
import { copyFileSync, replaceExtSync } from './util'
import { renderDocToPage } from './page'
import { get, removeFile } from './deps'
const DOCS_DIR = resolve(__dirname, '../docs')
const PAGES_DIR = resolve(__dirname, '../../pages')
const DEMOS_DIR = resolve(__dirname, '../../components/demos')
const MERMAID_DIR = resolve(__dirname, '../../static/images/mermaid')
const ASSETS_DIR = resolve(__dirname, '../../assets')
export function generatePages (file, stats) {
if (!file) {
rimraf.sync(PAGES_DIR)
rimraf.sync(DEMOS_DIR)
rimraf.sync(MERMAID_DIR)
rimraf.sync(resolve(__dirname, './deps.json'))
console.log('Regenerating all files...')
handleFile(DOCS_DIR)
console.log('...done.')
} else {
handleFile(file, stats)
}
}
function handleFile (file, stats) {
let segments = relative(DOCS_DIR, file).split(sep)
if (segments.some(segment => {
return segment.startsWith('_') || segment.startsWith('.')
})) {
return
}
let remove = stats ? stats.remove : false
let dir = stats ? stats.dir : statSync(file).isDirectory()
if (dir) {
if (remove) {
rimraf.sync(file)
return
}
let children = readdirpSync(file)
children.forEach(child => {
handleFile(child, remove)
})
return
}
let ext = extname(file).toLowerCase()
/* eslint-disable indent */
/* There seems to be something wrong with FECS here */
switch (ext) {
case '.md': {
if (remove) {
let relDest = replaceExtSync(relative(DOCS_DIR, file), 'vue')
rimraf.sync(resolve(PAGES_DIR, relDest))
console.log(`[${relDest}] removed.`)
} else {
let dest = relative(DOCS_DIR, file)
renderDocToPage(dest)
console.log(`[${dest}] synced.`)
}
break
}
case '.json': {
if (basename(file) === 'nav.json') {
copyFileSync(file, resolve(ASSETS_DIR, 'data', 'nav.json'))
console.log('[nav.json] synced.')
}
break
}
default: {
let relDest = relative(DOCS_DIR, file)
let dest = relDest.split(sep).indexOf('demo') === -1
? resolve(PAGES_DIR, relDest)
: resolve(DEMOS_DIR, relDest)
if (remove) {
rimraf.sync(dest)
console.log(`[${relDest}] removed.`)
} else {
copyFileSync(file, dest)
console.log(`[${relDest}] synced.`)
}
break
}
}
/* eslint-enable indent */
if (remove) {
removeFile(file)
return
}
let deps = get(file)
if (deps) {
Object.keys(deps).forEach(dep => {
handleFile(dep)
})
}
}

13
one/build/i18n.js Normal file
View File

@@ -0,0 +1,13 @@
export const DEFAULT_LOCALE = 'zh-Hans'
export const LOCALES = [
{
code: DEFAULT_LOCALE,
label: '简体中文'
},
{
code: 'en-US',
label: 'English (US)'
}
]
export const LOCALE_CODES = LOCALES.map(l => l.code)
export const RE_LOCALE = new RegExp(`^\\/(${LOCALE_CODES.join('|')})\\/`)

99
one/build/language.js Normal file
View File

@@ -0,0 +1,99 @@
/* eslint-disable fecs-camelcase */
/* eslint-disable babel/new-cap */
export function vue (hljs) {
const XML_IDENT_RE = '[A-Za-z0-9\\._:-]+'
const TAG_INTERNALS = {
endsWithParent: true,
illegal: /</,
relevance: 0,
contains: [
{
className: 'attr',
begin: XML_IDENT_RE,
relevance: 0
},
{
begin: /=\s*/,
relevance: 0,
contains: [
{
className: 'string',
endsParent: true,
variants: [
{begin: /"/, end: /"/},
{begin: /'/, end: /'/},
{begin: /[^\s"'=<>`]+/}
]
}
]
}
]
}
return {
case_insensitive: true,
contains: [
hljs.COMMENT(
'<!--',
'-->',
{
relevance: 10
}
),
{
className: 'tag',
/*
The lookahead pattern (?=...) ensures that 'begin' only matches
'<style' as a single word, followed by a whitespace or an
ending braket. The '$' is needed for the lexeme to be recognized
by hljs.subMode() that tests lexemes outside the stream.
*/
begin: '<style(?=\\s|>|$)',
end: '>',
keywords: {name: 'style'},
contains: [TAG_INTERNALS],
starts: {
end: '</style>',
returnEnd: true,
subLanguage: ['css', 'less', 'scss', 'stylus']
}
},
{
className: 'tag',
// See the comment in the <style tag about the lookahead pattern
begin: '<script(?=\\s|>|$)',
end: '>',
keywords: {name: 'script'},
contains: [TAG_INTERNALS],
starts: {
end: '</script>',
returnEnd: true,
subLanguage: ['javascript']
}
},
{
className: 'tag',
// See the comment in the <style tag about the lookahead pattern
begin: '<template(?=\\s|>|$)',
end: '>',
keywords: {name: 'template'},
contains: [TAG_INTERNALS],
starts: {
end: '</template>',
returnEnd: true,
subLanguage: ['html']
}
},
{
className: 'tag',
begin: '</?',
end: '/?>',
contains: [
{
className: 'name', begin: /[^/><\s]+/, relevance: 0
},
TAG_INTERNALS
]
}
]
}
}

94
one/build/page.js Normal file
View File

@@ -0,0 +1,94 @@
import { resolve, relative, join } from 'path'
import vfile from 'vfile'
import remark from 'remark'
import slug from 'remark-slug'
import frontmatter from 'remark-frontmatter'
import highlight from 'remark-highlight.js'
import shortcodes from 'remark-shortcodes'
import mermaid from 'one-remark-mermaid'
import remarkToRehype from 'remark-rehype'
import raw from 'rehype-raw'
import html from 'rehype-stringify'
import etpl from 'etpl'
import { readFileSync, writeFileSync, replaceExtSync } from './util'
import demo from './remark-demo'
import ref from './remark-ref'
import details from './remark-details'
import custom from './remark-custom'
import extractFrontmatter from './remark-extract-frontmatter'
import rehypePreviewImg from './rehype-preview-img'
import rehypeLink from './rehype-link'
import rehypeDemo from './rehype-demo'
import { add } from './deps'
import lowlight from 'lowlight'
import { vue } from './language'
const DOCS_DIR = resolve(__dirname, '../docs')
const PAGES_DIR = resolve(__dirname, '../../pages')
const STYLES_DIR = resolve(__dirname, '../../assets/styles')
const MERMAID_DIR = resolve(__dirname, '../../static/images/mermaid')
const PAGE_TPL = readFileSync(resolve(__dirname, '../templates/page.etpl'))
const renderPage = etpl.compile(PAGE_TPL)
lowlight.registerLanguage('vue', vue)
const md = remark()
.use(custom)
.use(details)
.use(ref)
.use(frontmatter)
.use(shortcodes)
.use(mermaid, {
cssFile: join(`${STYLES_DIR}`, 'mermaid-cli.css'),
theme: 'none'
})
.use(demo)
.use(extractFrontmatter)
.use(highlight)
.use(slug)
.use(remarkToRehype, { allowDangerousHTML: true })
.use(raw)
.use(rehypePreviewImg)
.use(rehypeLink)
.use(rehypeDemo)
.use(html, { allowDangerousHTML: true })
export function render (contents, path, data = {}) {
data.destinationDir = MERMAID_DIR
return md.processSync(vfile({ contents, path, data }))
}
function renderFile (file) {
return render(readFileSync(file), file)
}
export function renderDocToPage (file) {
let src = resolve(DOCS_DIR, file)
let dest = resolve(PAGES_DIR, replaceExtSync(file, 'vue'))
let { contents, data } = renderFile(src, dest)
let { demos = {}, components = {}, meta = {}, deps = {} } = data
Object.keys(deps || {}).forEach(dep => {
add({ [dep]: { [src]: true } })
})
let { layout, style } = meta
let componentList = Object.keys(components)
let demoList = Object.keys(demos)
let result = renderPage({
content: (contents || '').replace(/\n{3,}/g, '\n\n'),
demos: demoList.map(name => {
return {
name,
src: join('@/components/demos', relative(DOCS_DIR, demos[name].path))
}
}),
components: componentList,
boilerplate: demoList.length || componentList.length,
layout,
style: style || 'post'
})
writeFileSync(dest, result)
}

226
one/build/refBlock.js Normal file
View File

@@ -0,0 +1,226 @@
'use strict'
var trim = require('trim-trailing-lines')
module.exports = refBlock
var C_NEWLINE = '\n'
var C_TAB = '\t'
var C_SPACE = ' '
var C_CARET = '^'
var MIN_FENCE_COUNT = 3
var CODE_INDENT_COUNT = 4
function refBlock (eat, value, silent) {
var self = this
var length = value.length + 1
var index = 0
var subvalue = ''
var fenceCount
var marker
var character
var flag
var queue
var content
var exdentedContent
var closing
var exdentedClosing
var indent
var now
/* Eat initial spacing. */
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
subvalue += character
index++
}
indent = index
/* Eat the fence. */
character = value.charAt(index)
if (character !== C_CARET) {
return
}
index++
marker = character
fenceCount = 1
subvalue += character
while (index < length) {
character = value.charAt(index)
if (character !== marker) {
break
}
subvalue += character
fenceCount++
index++
}
if (fenceCount < MIN_FENCE_COUNT) {
return
}
/* Eat spacing before flag. */
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
subvalue += character
index++
}
/* Eat flag. */
flag = ''
queue = ''
while (index < length) {
character = value.charAt(index)
if (character === C_NEWLINE || character === C_CARET) {
break
}
if (character === C_SPACE || character === C_TAB) {
queue += character
} else {
flag += queue + character
queue = ''
}
index++
}
character = value.charAt(index)
if (character && character !== C_NEWLINE) {
return
}
if (silent) {
return true
}
now = eat.now()
now.column += subvalue.length
now.offset += subvalue.length
subvalue += flag
flag = self.decode.raw(self.unescape(flag), now)
if (queue) {
subvalue += queue
}
queue = ''
closing = ''
exdentedClosing = ''
content = ''
exdentedContent = ''
/* Eat content. */
while (index < length) {
character = value.charAt(index)
content += closing
exdentedContent += exdentedClosing
closing = ''
exdentedClosing = ''
if (character !== C_NEWLINE) {
content += character
exdentedClosing += character
index++
continue
}
/* Add the newline to `subvalue` if its the first
* character. Otherwise, add it to the `closing`
* queue. */
if (content) {
closing += character
exdentedClosing += character
} else {
subvalue += character
}
queue = ''
index++
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE) {
break
}
queue += character
index++
}
closing += queue
exdentedClosing += queue.slice(indent)
if (queue.length >= CODE_INDENT_COUNT) {
continue
}
queue = ''
while (index < length) {
character = value.charAt(index)
if (character !== marker) {
break
}
queue += character
index++
}
closing += queue
exdentedClosing += queue
if (queue.length < fenceCount) {
continue
}
queue = ''
while (index < length) {
character = value.charAt(index)
if (character !== C_SPACE && character !== C_TAB) {
break
}
closing += character
exdentedClosing += character
index++
}
if (!character || character === C_NEWLINE) {
break
}
}
subvalue += content + closing
return eat(subvalue)({
type: 'refblock',
id: flag || null,
value: trim(exdentedContent)
})
}

54
one/build/rehype-demo.js Normal file
View File

@@ -0,0 +1,54 @@
import visit from 'unist-util-visit'
import h from 'hastscript'
const RE_DEMO = /^one-demo-[a-f0-9]+/i
export default function attacher () {
return (tree, { data }) => {
visit(tree, 'element', (node, index, parent) => {
let { tagName } = node
let [name] = tagName.match(RE_DEMO) || []
if (name) {
let { code, desc, browser } = data.demos[name] || {}
if (!code) {
return
}
let demo = h('one-demo',
{
browser
},
[
node,
h(
'template',
{
slot: 'source'
},
h(
'div',
{
'v-pre': true
},
{
type: 'raw',
value: code
}
)
),
h(
'template',
{
slot: 'desc'
},
{
type: 'raw',
value: desc
}
)
])
parent.children.splice(index, 1, demo)
}
})
}
}

26
one/build/rehype-link.js Normal file
View File

@@ -0,0 +1,26 @@
import { resolve, relative } from 'path'
import visit from 'unist-util-visit'
import { RE_LOCALE } from './i18n'
const DOCS_DIR = resolve(__dirname, '../docs')
export default function attacher () {
return (tree, { path }) => {
let localPath = `/${relative(DOCS_DIR, path)}`
let [, locale] = localPath.match(RE_LOCALE) || []
if (!locale) {
return
}
visit(tree, 'element', node => {
let { tagName, properties, properties: { href } } = node
if (tagName !== 'a' || href.match(/^\w+:\/\//)) {
return
}
if (locale && href.indexOf('/') === 0) {
properties.href = `/${locale}${href}`
}
})
}
}

View File

@@ -0,0 +1,98 @@
import { resolve, join } from 'path'
import visit from 'unist-util-visit'
import h from 'hastscript'
import { removeClass, addClass, hasClass } from './rehype-util-class'
import { readFileSync } from './util'
const RE_SPACE = /[\w\r\n]*/
const MERMAID_DIR = resolve(__dirname, '../../static/images/mermaid')
export default function attacher () {
return tree => {
visit(tree, 'element', (node, index, { children }) => {
let { tagName, properties, properties: { src, alt, title, className } } = node
if (tagName !== 'img' || src.match(/^\w+:\/\//)) {
return
}
if (title && title.indexOf('mermaid') !== -1) {
let fig = h(
'figure.hero.mermaid',
{
'v-once': true
},
{
type: 'raw',
value: readFileSync(join(MERMAID_DIR, src))
}
)
children.splice(index, 1, fig)
return
}
src = src.replace(/(\.\.)?\/assets\//g, '/images/content/')
if (hasClass(node, 'preview')) {
removeClass(node, 'preview')
let fig = h(`figure${hasClass(node, 'hero') ? '.hero' : ''}`, [
h('div.preview', [
h('img', {
src,
alt,
title,
className,
srcset: `${src} 2x`
})
]),
...(title || alt) ? [
h('figcaption', [
...title ? [
h('p.caption', title)
] : [],
...alt ? [
h('p.desc', alt)
] : []
])
] : []
])
children.splice(index, 1, fig)
if (hasClass(node, 'bad')) {
removeClass(node, 'bad')
addClass(fig, 'bad')
let prev
let prevIndex = index - 1
while (true) {
prev = children[prevIndex]
if (!prev) {
break
}
// skip whitespace-only text nodes
if (prev.type === 'text' && RE_SPACE.test(prev.value)) {
prevIndex--
continue
}
break
}
if (prev) {
let prevPreview = (prev.children || [])[0]
if (prevPreview && hasClass(prevPreview, 'preview')) {
let prevImg = (prevPreview.children || [])[0]
if (hasClass(prevImg, 'good')) {
removeClass(prevImg, 'good')
addClass(prev, 'good')
children.splice(index - 1, 2, h('div.comparison', [prev, fig]))
}
}
}
}
} else {
properties.src = src
properties.srcset = `${src} 2x`
if (className && !className.length) {
delete properties.className
}
}
})
}
}

View File

@@ -0,0 +1,36 @@
import { remove, get, set } from 'lodash'
const CLASSNAME_PATH = 'properties.className'
export function toggleClass (node, name, add) {
let className = get(node, CLASSNAME_PATH)
if (!className) {
set(node, CLASSNAME_PATH, [])
className = node.properties.className
}
if (typeof add === 'boolean') {
if (add && !className.includes(name)) {
className.push(name)
} else if (!add) {
remove(className, c => c === name)
}
} else if (className.includes(name)) {
remove(className, c => c === name)
} else {
className.push(name)
}
}
export function addClass (node, name) {
toggleClass(node, name, true)
}
export function removeClass (node, name) {
toggleClass(node, name, false)
}
export function hasClass (node, name) {
let className = get(node, CLASSNAME_PATH, [])
return className.includes(name)
}

View File

@@ -0,0 +1,30 @@
import tokenizer from './customBlock'
import visit from 'unist-util-visit'
import { render } from './page'
const NAME = 'customblock'
export default function attacher () {
let proto = this.Parser.prototype
proto.blockTokenizers[NAME] = tokenizer
proto.interruptParagraph.push([NAME])
proto.interruptList.push([NAME])
proto.interruptBlockquote.push([NAME])
let methods = proto.blockMethods
methods.unshift(NAME)
return (tree, file) => {
let { path } = file
visit(tree, NAME, ({ className, value }, index, parent) => {
let { contents } = render(value, path, {})
className = className ? `${className} custom-block` : 'custom-block'
parent.children.splice(index, 1, {
type: 'html',
value: `<div class="${className}">${contents}</div>`
})
})
}
}

125
one/build/remark-demo.js Normal file
View File

@@ -0,0 +1,125 @@
import { resolve, relative, dirname } from 'path'
import { existsSync } from 'fs'
import visit from 'unist-util-visit'
import { readFileSync, hash } from './util'
import { render } from './page'
import { parseComponent } from 'vue-template-compiler'
import { escape } from 'lodash'
import { RE_LOCALE } from './i18n'
const DOCS_DIR = resolve(__dirname, '../docs')
export default function attacher () {
return (tree, file) => {
let { path, data: fileData } = file
visit(tree, 'shortcode', node => {
let {
identifier,
data: nodeData,
attributes: { src, browser } = {}
} = node
if (identifier !== 'demo' || !src) {
return
}
if (!nodeData) {
node.data = nodeData = {}
}
if (!fileData) {
file.data = fileData = {}
}
if (!fileData.demos) {
fileData.demos = {}
}
if (!fileData.deps) {
fileData.deps = {}
}
let demoPath =
src.indexOf('/') === 0
? resolve(DOCS_DIR, src.slice(1))
: resolve(dirname(path), src)
if (!existsSync(demoPath)) {
console.warn(`Demo not found at '${demoPath}'`)
return
}
fileData.deps[demoPath] = true
let name = getComponentName(demoPath)
nodeData.hName = name
let localPath = `/${relative(DOCS_DIR, path)}`
let [, locale] = localPath.match(RE_LOCALE) || []
let { content, doc } = extractDoc(demoPath, { locale })
fileData.demos[name] = {
path: demoPath,
browser,
code: render('```vue\n' + content + '\n```', demoPath).contents,
desc: render(doc, demoPath).contents
}
})
}
}
function getComponentName (path) {
return `one-demo-${hash(path)}`
}
function extractDoc (file, { locale }) {
let content = readFileSync(file)
let vueComponent = parseComponent(content)
let { customBlocks } = vueComponent
let i = customBlocks.findIndex(({ type, attrs }) => {
return ((locale && locale === attrs.locale) || !locale) && type === 'docs'
})
let doc = null
if (i !== -1) {
doc = customBlocks[i].content
}
return {
content: stringifyVueComponent(vueComponent),
doc
}
}
function stringifyVueComponent (component) {
let content = []
let { template, script, styles } = component
if (template) {
content.push(
`<template${stringifyAttrs(template.attrs)}>${
template.content
}</template>`
)
}
if (script) {
content.push(
`<script${stringifyAttrs(script.attrs)}>${script.content}</script>`
)
}
styles.filter(({ attrs }) => !attrs.docs).forEach(style => {
content.push(
`<style${stringifyAttrs(style.attrs)}>${style.content}</style>`
)
})
return content.join('\n\n')
}
function stringifyAttrs (attrs) {
let result = Object.keys(attrs)
.map(key => {
let val = attrs[key]
if (typeof val !== 'boolean') {
return `${escape(key)}="${escape(val)}"`
}
return val ? `${escape(key)}` : ''
})
.join(' ')
return result ? ` ${result}` : ''
}

View File

@@ -0,0 +1,39 @@
import tokenizer from './details'
import visit from 'unist-util-visit'
import { escape } from 'lodash'
import { render } from './page'
const NAME = 'details'
export default function attacher () {
let proto = this.Parser.prototype
proto.blockTokenizers[NAME] = tokenizer
proto.interruptParagraph.push([NAME])
proto.interruptList.push([NAME])
proto.interruptBlockquote.push([NAME])
let methods = proto.blockMethods
methods.unshift(NAME)
return (tree, file) => {
let { path, data } = file
if (!data) {
file.data = data = {}
}
if (!data.components) {
data.components = {}
}
visit(tree, NAME, ({ summary, value }, index, parent) => {
data.components['OneDetails'] = true
let { contents } = render(value, path, data)
parent.children.splice(index, 1, {
type: 'html',
value: `<one-details summary="${escape(summary)}">${contents}</one-details>`
})
})
}
}

View File

@@ -0,0 +1,16 @@
import visit from 'unist-util-visit'
import remove from 'unist-util-remove'
import yaml from 'js-yaml'
export default function attacher () {
return (tree, file) => {
let { data } = file
visit(tree, 'yaml', node => {
data.meta = yaml.safeLoad(node.value)
return visit.EXIT
})
remove(tree, ['yaml', 'toml'])
}
}

49
one/build/remark-ref.js Normal file
View File

@@ -0,0 +1,49 @@
import tokenizer from './refBlock'
import visit from 'unist-util-visit'
import remove from 'unist-util-remove'
import { render } from './page'
const NAME = 'refblock'
const RE_REF = /^\^([-_a-z0-9]+)/i
export default function attacher () {
let proto = this.Parser.prototype
proto.blockTokenizers[NAME] = tokenizer
proto.interruptParagraph.push([NAME])
proto.interruptList.push([NAME])
proto.interruptBlockquote.push([NAME])
let methods = proto.blockMethods
methods.unshift(NAME)
return (tree, file) => {
let { path, data } = file
if (!data) {
file.data = data = {}
}
if (!data.refs) {
data.refs = {}
}
visit(tree, NAME, ({ id, value }) => {
let { contents } = render(value, path, data)
data.refs[id] = contents
})
remove(tree, NAME)
visit(tree, 'linkReference', (node, index, parent) => {
let { identifier } = node
let [match, id] = identifier.match(RE_REF)
if (!match || !id || !data.refs[id]) {
return
}
parent.children.splice(index, 1, {
type: 'html',
value: data.refs[id]
})
})
}
}

29
one/build/util.js Normal file
View File

@@ -0,0 +1,29 @@
import { readFileSync as fsReadFileSync, writeFileSync as fsWriteFileSync } from 'fs'
import { dirname } from 'path'
import mkdirp from 'mkdirp'
import crypto from 'crypto'
export function readFileSync (file) {
return fsReadFileSync(file, 'utf8')
}
export function writeFileSync (file, content) {
mkdirp.sync(dirname(file))
fsWriteFileSync(file, content, 'utf8')
}
export function copyFileSync (src, dest) {
mkdirp.sync(dirname(dest))
writeFileSync(dest, readFileSync(src))
}
const RE_EXT = /\.([^.]+)$/
export function replaceExtSync (file, ext) {
return file.replace(RE_EXT, `.${ext}`)
}
export function hash (path) {
let hash = crypto.createHash('sha1')
hash.update(path)
return hash.digest('hex').substring(0, 7)
}

33
one/build/watch.js Normal file
View File

@@ -0,0 +1,33 @@
import { resolve } from 'path'
import { debounce } from 'lodash'
import chokidar from 'chokidar'
import { generatePages } from './generator'
let watcher = chokidar.watch(resolve(__dirname, '../docs'), {
ignoreInitial: true
})
watcher
.on('all', debounce(generate, 1000))
function generate (type, path) {
switch (type) {
case 'add':
generatePages(path, { dir: false, remove: false })
break
case 'addDir':
generatePages(path, { dir: true, remove: false })
break
case 'change':
generatePages(path)
break
case 'unlink':
generatePages(path, { dir: false, remove: true })
break
case 'unlinkDir':
generatePages(path, { dir: true, remove: true })
break
default:
break
}
}