Open Aratramba opened 6 years ago
add output data json file alongisde component html
{
"height": 100,
"description": "<p></p>",
"source": "…"
}
add vue single file component for embedding components
<template>
<div class="component">
<!-- tools -->
<div class="component__tools">
<!-- code button -->
<button v-on:click="toggleCodeView" class="component__tools__button" v-bind:class="{ 'active': showsource }" v-if="jsonLoaded && source">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<path d="M0.7 9.3l4.8-4.8 1.4 1.42-4.060 4.080 4.070 4.070-1.41 1.42-5.5-5.49 0.7-0.7zM19.3 10.7l0.7-0.7-5.49-5.49-1.4 1.42 4.050 4.070-4.070 4.070 1.41 1.42 4.78-4.78z"></path>
</svg>
<span>code</span>
</button>
<!-- link button -->
<a :href="htmlFile" target="_blank" class="component__tools__button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<path d="M9.26 13c-0.167-0.286-0.266-0.63-0.266-0.996 0-0.374 0.103-0.724 0.281-1.023l-0.005 0.009c1.549-0.13 2.757-1.419 2.757-2.99 0-1.657-1.343-3-3-3-0.009 0-0.019 0-0.028 0l0.001-0h-4c-1.657 0-3 1.343-3 3s1.343 3 3 3v0h0.080c-0.053 0.301-0.083 0.647-0.083 1s0.030 0.699 0.088 1.036l-0.005-0.036h-0.080c-2.761 0-5-2.239-5-5s2.239-5 5-5v0h4c0.039-0.001 0.084-0.002 0.13-0.002 2.762 0 5.002 2.239 5.002 5.002 0 2.717-2.166 4.927-4.865 5l-0.007 0zM10.74 7c0.167 0.286 0.266 0.63 0.266 0.996 0 0.374-0.103 0.724-0.281 1.023l0.005-0.009c-1.549 0.13-2.757 1.419-2.757 2.99 0 1.657 1.343 3 3 3 0.009 0 0.019-0 0.028-0l-0.001 0h4c1.657 0 3-1.343 3-3s-1.343-3-3-3v0h-0.080c0.053-0.301 0.083-0.647 0.083-1s-0.030-0.699-0.088-1.036l0.005 0.036h0.080c2.761 0 5 2.239 5 5s-2.239 5-5 5v0h-4c-0.039 0.001-0.084 0.002-0.13 0.002-2.762 0-5.002-2.239-5.002-5.002 0-2.717 2.166-4.927 4.865-5l0.007-0z"></path>
</svg>
<span>{{ htmlFile }}</span>
</a>
</div>
<!-- code view -->
<div class="component__code" v-if="showsource && jsonLoaded && source">
<pre class="language-html"><code v-html="prettifyHTML(source)"></code></pre>
</div>
<!-- component iframe -->
<div class="component__preview" v-show="!showsource">
<!-- resizer -->
<span class="component__preview__handle">
<span class="component__preview__handle__overlay"></span>
<svg class="component__preview__handle__icon" width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12c-1.105 0-2-0.895-2-2s0.895-2 2-2v0c1.105 0 2 0.895 2 2s-0.895 2-2 2v0zM10 6c-1.105 0-2-0.895-2-2s0.895-2 2-2v0c1.105 0 2 0.895 2 2s-0.895 2-2 2v0zM10 18c-1.105 0-2-0.895-2-2s0.895-2 2-2v0c1.105 0 2 0.895 2 2s-0.895 2-2 2v0z"></path>
</svg>
</span>
<!-- height spacer -->
<div ref="iframe_spacer" v-if="!htmlLoaded" v-bind:style="{ height: height + 'px' }"></div>
<!-- component iframe -->
<iframe class="component__preview__iframe" ref="iframe" v-if="inview" :src="htmlFile" frameborder="0" scrolling="no" v-on:load="onIframeLoad"></iframe>
</div>
</div>
</template>
<script>
export default {
props: ["name"],
data() {
return {
htmlLoaded: false,
jsonLoaded: false,
height: 0,
source: null,
showsource: false,
inview: false,
htmlFile: this.getHTMLFilePath(this.$props.name),
jsonFile: this.getJSONFilePath(this.$props.name),
};
},
mounted() {
/**
* set in view observer for async loading iframe
*/
var observer = new IntersectionObserver(
function(entries, observer) {
entries.forEach(
function(entry) {
if (entry.isIntersecting) {
if (!this.inview) {
this.inview = true;
}
}
}.bind(this)
);
}.bind(this)
);
observer.observe(this.$el);
/**
* get meta info from lib/file.json
*/
fetch(this.jsonFile).then((res) => {
if (res.status === 200) {
return res.json();
}
}).then((obj) => {
this.jsonLoaded = true;
if (obj) {
this.height = obj.height;
this.source = obj.source;
}
}).catch((err) => {});
},
methods: {
/**
* get html file path
*/
getHTMLFilePath: function(str) {
return "/lib/" + this.slugify(str) + ".html";
},
/**
* get json file path
*/
getJSONFilePath: function(str) {
return "/lib/" + this.slugify(str) + ".json";
},
/**
* beautify and highlight html
*/
prettifyHTML: function(str) {
return window.Prism.highlight(window.html_beautify(str), window.Prism.languages.markup)
},
/**
* create slug-from-string
*/
slugify: function(str) {
return str
.toString()
.toLowerCase()
.replace(/\s+/g, "-") // Replace spaces with -
.replace(/[^\w-]+/g, "") // Remove all non-word chars
.replace(/--+/g, "-") // Replace multiple - with single -
.replace(/^-+/, "") // Trim - from start of text
.replace(/-+$/, ""); // Trim - from end of text
},
/**
* toggle code/preview view
*/
toggleCodeView: function (e) {
this.showsource = !this.showsource;
if (!this.showsource) {
window.iFrameResize({ checkOrigin: false }, this.$refs.iframe);
}
},
/**
* iframe loaded handler
*/
onIframeLoad: function(e) {
// resize iframe
window.iFrameResize({ checkOrigin: false }, this.$refs.iframe);
// make preview draggable
window.interact(this.$el.querySelector(".component__preview")).resizable({
edges: {
left: false,
right: ".component__preview__handle",
bottom: false,
top: false
},
onmove: function(e) {
e.target.style.width = e.rect.width - 24 + "px";
e.target.querySelector('iframe').contentWindow.parentIFrame.size();
if (!e.target.classList.contains("component-is-resizing")) {
e.target.classList.add("component-is-resizing");
}
},
onend: function(e) {
e.target.querySelector("iframe").contentWindow.parentIFrame.size();
if (e.target.classList.contains("component-is-resizing")) {
e.target.classList.remove("component-is-resizing");
}
}
});
// set loaded state
this.htmlLoaded = true;
}
}
};
</script>
<style scoped>
iframe {
width: 100%;
}
.component {
--grey: #eaecef;
--darkgrey: #111;
margin-top: 20px;
margin-bottom: 150px;
}
/**
* Tools
*/
.component__tools {
display: flex;
}
.component__tools__button {
border: 1px solid var(--grey);
border-radius: 3px;
padding: 6px 8px;
margin-left: 5px;
display: inline-flex;
outline: 0;
text-decoration: none !important;
cursor: pointer;
}
.component__tools__button:hover,
.component__tools__button.active {
background-color: var(--grey);
}
.component__tools__button svg {
margin: auto;
}
.component__tools__button span {
font-size: 12px;
font-weight: normal;
color: var(--darkgrey);
margin: auto 0 auto 6px;
}
/**
* Code
*/
.component__code {
max-width: 100vw;
margin-top: 20px;
width: 1200px;
transform: translateX(-50%) translateX(370px);
position: relative;
min-width: 275px;
}
/**
* Preview
*/
.component__preview {
max-width: 100vw;
margin-top: 20px;
width: 1200px;
transform: translateX(-50%) translateX(370px);
position: relative;
min-width: 275px;
padding-right: 24px;
border: 1px solid var(--grey);
}
.component__preview__iframe {
display: block;
width: 100%;
}
/**
* Resize handle
*/
.component__preview__handle {
display: block;
position: absolute;
width: 24px;
height: 100%;
right: 0;
top: 0;
bottom: 0;
}
.component-is-resizing >>> .component__preview__handle {
left: 0;
width: 100%;
}
.component__preview__handle__overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 24px;
content: "";
background-color: white;
transition: background-color 0.15s, border-color 0.15s;
border-left: 1px solid var(--grey);
}
.component__preview__handle:hover >>> .component__preview__handle__overlay,
.component-is-resizing >>> .component__preview__handle__overlay {
background-color: #f5f5f5;
border-color: var(--grey);
}
/**
* Resize handle icon
*/
.component__preview__handle__icon {
display: block;
width: 20px;
height: 20px;
position: absolute;
top: 50%;
right: 2px;
transform: translate(0, -50%);
opacity: 0.25;
transition: opacity 0.15s;
}
.component-is-resizing >>> .component__preview__handle__icon {
opacity: 0.5;
}
</style>
Add copyable vuepress config
module.exports = {
title: "Design Manual",
description: "this is my design manual",
head: [
['script', { src: 'https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/3.6.0/iframeResizer.min.js' }],
['script', { src: 'https://cdnjs.cloudflare.com/ajax/libs/vanilla-lazyload/10.14.0/lazyload.min.js' }],
['script', { src: 'https://unpkg.com/interactjs@1.3.3/dist/interact.min.js' }],
['script', { src: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.14.0/prism.min.js' }],
['script', { src: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.14.0/components/prism-markup-templating.js' }],
['script', { src: 'https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.7.5/beautify-html.min.js' }],
],
themeConfig: {
nav: [
{ text: "Home", link: "/" },
{ text: "Guide", link: "/guide/" },
{ text: "External", link: "https://google.com" }
],
sidebar: [
{
title: "Components",
collapsable: false,
children: ["/"]
},
{
title: "Content",
children: [
/* ... */
]
},
{
title: "Whatever",
children: [
/* ... */
]
}
]
}
};
let Design Manual only do the generating of components, let Vuepress handle the pages.