309 lines
13 KiB
HTML
309 lines
13 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.css" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/styles/github-gist.min.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
|
|
<script src="https://unpkg.com/vue-async-computed@3.8.1"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/highlight.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
|
|
<style>
|
|
@media (max-width: 767px) {
|
|
.markdown-body {
|
|
padding: 15px;
|
|
}
|
|
|
|
#search {
|
|
max-width: 85%;
|
|
}
|
|
}
|
|
body {
|
|
overflow: scroll;
|
|
}
|
|
.markdown-body {
|
|
box-sizing: border-box;
|
|
min-width: 200px;
|
|
max-width: 980px;
|
|
margin: 0 auto;
|
|
padding: 45px;
|
|
}
|
|
#search {
|
|
border: 1px solid #d1d5da;
|
|
padding-left: 30px;
|
|
overflow: hidden;
|
|
}
|
|
.searchCondition {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
}
|
|
.searchCondition > div {
|
|
margin-right: 30px;
|
|
}
|
|
.header-link {
|
|
position: relative;
|
|
}
|
|
.header-link:hover::before {
|
|
position: absolute;
|
|
left: -2em;
|
|
padding-right: 0.5em;
|
|
content: '\2002\00a7\2002';
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<article class="markdown-body">
|
|
<div class="searchCondition">
|
|
<div>
|
|
<form style="display:flex;">
|
|
<label for="search" style="margin-right: 3px;" >search:</label>
|
|
<div style="position: relative;">
|
|
<input id="search" placeholder="Search all options" v-model="searchCondition">
|
|
<svg style="position: absolute; left: 8px; top: 7px;" class="octicon octicon-search subnav-search-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
|
|
<path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"></path>
|
|
</svg>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div>
|
|
<label for="stable">stable: </label>
|
|
<input type="checkbox" id="stable" v-model="shouldStable">
|
|
</div>
|
|
<div>
|
|
<label for="version">version: </label>
|
|
<select name="version" id="version" v-model="version">
|
|
<option v-for="option in versionOptions" v-bind:value="option">
|
|
{{ option }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div v-html="aboutHtml"></div>
|
|
<div v-html="configurationAboutHtml"></div>
|
|
<div v-html="outputHtml"></div>
|
|
</article>
|
|
</div>
|
|
<script>
|
|
const RusfmtTagsUrl = 'https://api.github.com/repos/rust-lang/rustfmt/tags';
|
|
const RustfmtLatestUrl = 'https://api.github.com/repos/rust-lang/rustfmt/releases/latest';
|
|
const UrlHash = window.location.hash.replace(/^#/, '');
|
|
const queryParams = new URLSearchParams(window.location.search);
|
|
const searchParam = queryParams.get('search');
|
|
const searchTerm = null !== searchParam ? searchParam : '';
|
|
const versionParam = queryParams.get('version');
|
|
const parseVersionParam = (version) => {
|
|
if (version === 'master') return 'master';
|
|
if (version.startsWith('v')) return version;
|
|
return `v${version}`;
|
|
};
|
|
const versionNumber = null !== versionParam ? parseVersionParam(versionParam) : 'master';
|
|
new Vue({
|
|
el: '#app',
|
|
data: {
|
|
aboutHtml: '',
|
|
configurationAboutHtml: '',
|
|
configurationDescriptions: [],
|
|
searchCondition: searchTerm,
|
|
shouldStable: false,
|
|
version: versionNumber,
|
|
oldVersion: undefined,
|
|
versionOptions: ['master'],
|
|
scrolledOnce: false,
|
|
},
|
|
asyncComputed: {
|
|
async updateVersion() {
|
|
let latest;
|
|
try {
|
|
latest = (await axios.get(RustfmtLatestUrl)).data;
|
|
} catch(err) {
|
|
console.log(err);
|
|
return;
|
|
}
|
|
this.version = latest.name;
|
|
},
|
|
async outputHtml() {
|
|
if (this.version !== this.oldVersion) {
|
|
const ConfigurationMdUrl =
|
|
`https://raw.githubusercontent.com/rust-lang/rustfmt/${this.version}/Configurations.md`;
|
|
let res;
|
|
try {
|
|
res = await axios.get(ConfigurationMdUrl).catch(e => { throw e });
|
|
} catch(e) {
|
|
this.handleReqFailure(e);
|
|
return;
|
|
}
|
|
const {
|
|
about,
|
|
configurationAbout,
|
|
configurationDescriptions
|
|
} = parseMarkdownAst(res.data);
|
|
this.aboutHtml = marked.parser(about);
|
|
this.configurationAboutHtml = marked.parser(configurationAbout);
|
|
this.configurationDescriptions = configurationDescriptions;
|
|
this.oldVersion = this.version;
|
|
}
|
|
|
|
const ast = this.configurationDescriptions
|
|
.filter(({ head, text, stable }) => {
|
|
if (text.includes(this.searchCondition) === false &&
|
|
head.includes(this.searchCondition) === false) {
|
|
return false;
|
|
}
|
|
return (this.shouldStable)
|
|
? stable === true
|
|
: true;
|
|
})
|
|
.reduce((stack, { value }) => {
|
|
return stack.concat(value);
|
|
}, []);
|
|
ast.links = {};
|
|
|
|
queryParams.set('version', this.version);
|
|
queryParams.set('search', this.searchCondition);
|
|
const curUrl = window.location.pathname +
|
|
'?' + queryParams.toString() + window.location.hash;
|
|
history.pushState(null, '', curUrl);
|
|
|
|
const renderer = new marked.Renderer();
|
|
renderer.heading = function(text, level) {
|
|
const id = htmlToId(text);
|
|
return `<h${level}>
|
|
<a id="${id}" href="#${id}" name="${id}" class="header-link">${text}</a>
|
|
</h${level}>`;
|
|
};
|
|
|
|
return marked.parser(ast, {
|
|
highlight(code, lang) {
|
|
return hljs.highlight(lang ? lang : 'rust', code).value;
|
|
},
|
|
headerIds: true,
|
|
headerPrefix: '',
|
|
renderer,
|
|
});
|
|
}
|
|
},
|
|
created: async function() {
|
|
let tags;
|
|
try {
|
|
tags = (await axios.get(RusfmtTagsUrl)).data;
|
|
} catch(e) {
|
|
this.handleReqFailure(e);
|
|
return;
|
|
}
|
|
|
|
const excludedTagVersions = new Set(['v0.7', 'v0.8.1']);
|
|
|
|
const tagOptions = tags
|
|
.map(tag => tag.name)
|
|
.filter(tag => tag.startsWith('v') && !excludedTagVersions.has(tag));
|
|
this.versionOptions = this.versionOptions.concat(tagOptions);
|
|
},
|
|
updated() {
|
|
if (UrlHash === '') return;
|
|
this.$nextTick(() => {
|
|
const target = document.querySelector(`#${UrlHash}`);
|
|
if (target != null && !this.scrolledOnce) {
|
|
target.scrollIntoView(true);
|
|
this.scrolledOnce = true;
|
|
}
|
|
});
|
|
},
|
|
methods: {
|
|
handleReqFailure(e) {
|
|
if (e.response.status === 404) {
|
|
this.aboutHtml =
|
|
"<p>Failed to get configuration options for this version, please select the version from the dropdown above.</p>";
|
|
} else if (
|
|
e.response.status === 403 &&
|
|
e.response.headers["X-RateLimit-Remaining"] === 0
|
|
) {
|
|
const resetDate = new Date(
|
|
e.response.headers['X-RateLimit-Reset'] * 1000
|
|
).toLocaleString();
|
|
this.aboutHtml =
|
|
`<p>You have hit the GitHub API rate limit; documentation cannot be updated.` +
|
|
`<p>The rate limit will be reset at ${resetDate}.</p>`;
|
|
} else {
|
|
this.aboutHtml =
|
|
`<p>Ecountered an error when fetching documentation data:</p>` +
|
|
`<pre><code>${e.response.data}</code></pre>` +
|
|
`<p>We would appreciate <a href="https://github.com/rust-lang/rustfmt/issues/new?template=bug_report.md">a bug report</a>.` +
|
|
`<p>Try refreshing the page.</p>`;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
const extractDepthOnes = (ast) => {
|
|
return ast.reduce((stack, next) => {
|
|
if (next.depth === 1) {
|
|
stack.push([]);
|
|
}
|
|
const lastIndex = stack.length - 1;
|
|
stack[lastIndex].push(next);
|
|
return stack;
|
|
}, []);
|
|
}
|
|
const extractDepthTwos = (ast) => {
|
|
return ast.map((elem) => {
|
|
return elem.reduce((stack, next) => {
|
|
if (next.depth === 2) {
|
|
stack.push([]);
|
|
}
|
|
const lastIndex = stack.length - 1;
|
|
stack[lastIndex].push(next);
|
|
return stack;
|
|
},
|
|
[[]]);
|
|
});
|
|
}
|
|
const createHeadAndValue = (ast) => {
|
|
return ast.map((elem) => {
|
|
return elem.map((val) => {
|
|
return {
|
|
head: val[0].text,
|
|
value: val,
|
|
stable: val.some((elem) => {
|
|
return elem.type === "list" &&
|
|
!!elem.raw &&
|
|
elem.raw.includes("**Stable**: Yes");
|
|
}),
|
|
text: val.reduce((result, next) => {
|
|
return next.text != null
|
|
? `${result} ${next.text}`
|
|
: result;
|
|
}, '')
|
|
}
|
|
});
|
|
})
|
|
}
|
|
const parseMarkdownAst = (rawMarkdown) => {
|
|
const ast = marked.lexer(rawMarkdown);
|
|
const depthOnes = extractDepthOnes(ast);
|
|
const depthTwos = extractDepthTwos(depthOnes);
|
|
const [
|
|
abouts, configurations
|
|
] = createHeadAndValue(depthTwos);
|
|
const about = abouts[0].value;
|
|
about.links = {};
|
|
const [
|
|
configurationAbout, ...configurationDescriptions
|
|
] = configurations;
|
|
configurationAbout.value.links = {};
|
|
|
|
return {
|
|
about,
|
|
configurationAbout: configurationAbout.value,
|
|
configurationDescriptions
|
|
};
|
|
}
|
|
function htmlToId(text) {
|
|
const tmpl = document.createElement('template');
|
|
tmpl.innerHTML = text.trim();
|
|
return encodeURIComponent(CSS.escape(tmpl.content.textContent));
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|