33 < head >
44 < meta name ="viewport " content ="width=device-width ">
55 < link rel ="stylesheet " href ="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.css " />
6+ < link rel ="stylesheet " href ="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/styles/github-gist.min.css ">
67 < script src ="https://cdn.jsdelivr.net/npm/marked/marked.min.js "> </ script >
78 < script src ="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js "> </ script >
9+ < script src ="https://unpkg.com/vue-async-computed@3.8.1 "> </ script >
10+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/highlight.min.js "> </ script >
811 < script src ="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js "> </ script >
912 < style >
1013 @media (max-width : 767px ) {
3841 .searchCondition > div {
3942 margin-right : 30px ;
4043 }
44+ .header-link {
45+ position : relative;
46+ }
47+ .header-link : hover ::before {
48+ position : absolute;
49+ left : -2em ;
50+ padding-right : 0.5em ;
51+ content : '\2002\00a7\2002' ;
52+ }
4153 </ style >
4254 </ head >
4355 < body >
5971 < label for ="stable "> stable: </ label >
6072 < input type ="checkbox " id ="stable " v-model ="shouldStable ">
6173 </ div >
74+ < div >
75+ < label for ="version "> version: </ label >
76+ < select name ="version " id ="version " v-model ="version ">
77+ < option v-for ="option in versionOptions " v-bind:value ="option ">
78+ {{ option }}
79+ </ option >
80+ </ select >
81+ </ div >
6282 </ div >
6383 < div v-html ="aboutHtml "> </ div >
6484 < div v-html ="configurationAboutHtml "> </ div >
6585 < div v-html ="outputHtml "> </ div >
6686 </ article >
6787 </ div >
6888 < script >
69- const ConfigurationMdUrl = 'https://raw.githubusercontent.com/rust-lang/rustfmt/master/Configurations.md' ;
89+ const RusfmtTagsUrl = 'https://api.github.com/repos/rust-lang/rustfmt/tags' ;
90+ const RustfmtLatestUrl = 'https://api.github.com/repos/rust-lang/rustfmt/releases/latest' ;
7091 const UrlHash = window . location . hash . replace ( / ^ # / , '' ) ;
92+ const queryParams = new URLSearchParams ( window . location . search ) ;
93+ const searchParam = queryParams . get ( 'search' ) ;
94+ const searchTerm = null !== searchParam ? searchParam : '' ;
95+ const versionParam = queryParams . get ( 'version' ) ;
96+ const parseVersionParam = ( version ) => {
97+ if ( version === 'master' ) return 'master' ;
98+ if ( version . startsWith ( 'v' ) ) return version ;
99+ return `v${ version } ` ;
100+ } ;
101+ const versionNumber = null !== versionParam ? parseVersionParam ( versionParam ) : 'master' ;
71102 new Vue ( {
72103 el : '#app' ,
73- data ( ) {
74- const configurationDescriptions = [ ] ;
75- configurationDescriptions . links = { } ;
76- return {
77- aboutHtml : '' ,
78- configurationAboutHtml : '' ,
79- searchCondition : UrlHash ,
80- configurationDescriptions ,
81- shouldStable : false
82- }
104+ data : {
105+ aboutHtml : '' ,
106+ configurationAboutHtml : '' ,
107+ configurationDescriptions : [ ] ,
108+ searchCondition : searchTerm ,
109+ shouldStable : false ,
110+ version : versionNumber ,
111+ oldVersion : undefined ,
112+ versionOptions : [ 'master' ] ,
113+ scrolledOnce : false ,
83114 } ,
84- computed : {
85- outputHtml ( ) {
115+ asyncComputed : {
116+ async updateVersion ( ) {
117+ let latest ;
118+ try {
119+ latest = ( await axios . get ( RustfmtLatestUrl ) ) . data ;
120+ } catch ( err ) {
121+ console . log ( err ) ;
122+ return ;
123+ }
124+ if ( versionParam == null ) {
125+ this . version = latest . name ;
126+ }
127+ } ,
128+ async outputHtml ( ) {
129+ if ( this . version !== this . oldVersion ) {
130+ const ConfigurationMdUrl =
131+ `https://raw.githubusercontent.com/rust-lang/rustfmt/${ this . version } /Configurations.md` ;
132+ let res ;
133+ try {
134+ res = await axios . get ( ConfigurationMdUrl ) . catch ( e => { throw e } ) ;
135+ } catch ( e ) {
136+ this . handleReqFailure ( e ) ;
137+ return ;
138+ }
139+ const {
140+ about,
141+ configurationAbout,
142+ configurationDescriptions
143+ } = parseMarkdownAst ( res . data ) ;
144+ this . aboutHtml = marked . parser ( about ) ;
145+ this . configurationAboutHtml = marked . parser ( configurationAbout ) ;
146+ this . configurationDescriptions = configurationDescriptions ;
147+ this . oldVersion = this . version ;
148+ }
149+
86150 const ast = this . configurationDescriptions
87- . filter ( ( { head, text, stable } ) => {
88-
89- if (
90- text . includes ( this . searchCondition ) === false &&
91- head . includes ( this . searchCondition ) === false
92- ) {
93- return false ;
94- }
95- return ( this . shouldStable )
96- ? stable === true
97- : true ;
98- } )
99- . reduce ( ( stack , { value } ) => {
100- return stack . concat ( value ) ;
101- } , [ ] ) ;
151+ . filter ( ( { head, text, stable } ) => {
152+ if ( text . includes ( this . searchCondition ) === false &&
153+ head . includes ( this . searchCondition ) === false ) {
154+ return false ;
155+ }
156+ return ( this . shouldStable )
157+ ? stable === true
158+ : true ;
159+ } )
160+ . reduce ( ( stack , { value } ) => {
161+ return stack . concat ( value ) ;
162+ } , [ ] ) ;
102163 ast . links = { } ;
103- return marked . parser ( ast ) ;
164+
165+ queryParams . set ( 'version' , this . version ) ;
166+ queryParams . set ( 'search' , this . searchCondition ) ;
167+ const curUrl = window . location . pathname +
168+ '?' + queryParams . toString ( ) + window . location . hash ;
169+ history . pushState ( null , '' , curUrl ) ;
170+
171+ const renderer = new marked . Renderer ( ) ;
172+ renderer . heading = function ( text , level ) {
173+ const id = htmlToId ( text ) ;
174+ return `<h${ level } >
175+ <a id="${ id } " href="#${ id } " name="${ id } " class="header-link">${ text } </a>
176+ </h${ level } >` ;
177+ } ;
178+
179+ return marked . parser ( ast , {
180+ highlight ( code , lang ) {
181+ return hljs . highlight ( lang ? lang : 'rust' , code ) . value ;
182+ } ,
183+ headerIds : true ,
184+ headerPrefix : '' ,
185+ renderer,
186+ } ) ;
104187 }
105188 } ,
106189 created : async function ( ) {
107- const res = await axios . get ( ConfigurationMdUrl ) ;
108- const {
109- about,
110- configurationAbout,
111- configurationDescriptions
112- } = parseMarkdownAst ( res . data ) ;
113- this . aboutHtml = marked . parser ( about ) ;
114- this . configurationAboutHtml = marked . parser ( configurationAbout ) ;
115- this . configurationDescriptions = configurationDescriptions ;
190+ let tags ;
191+ try {
192+ tags = ( await axios . get ( RusfmtTagsUrl ) ) . data ;
193+ } catch ( e ) {
194+ this . handleReqFailure ( e ) ;
195+ return ;
196+ }
197+
198+ const excludedTagVersions = new Set ( [ 'v0.7' , 'v0.8.1' ] ) ;
199+
200+ const tagOptions = tags
201+ . map ( tag => tag . name )
202+ . filter ( tag => tag . startsWith ( 'v' ) && ! excludedTagVersions . has ( tag ) ) ;
203+ this . versionOptions = this . versionOptions . concat ( tagOptions ) ;
116204 } ,
117- mounted ( ) {
205+ updated ( ) {
118206 if ( UrlHash === '' ) return ;
119- const interval = setInterval ( ( ) => {
207+ this . $nextTick ( ( ) => {
120208 const target = document . querySelector ( `#${ UrlHash } ` ) ;
121- if ( target != null ) {
209+ if ( target != null && ! this . scrolledOnce ) {
122210 target . scrollIntoView ( true ) ;
123- clearInterval ( interval ) ;
211+ this . scrolledOnce = true ;
212+ }
213+ } ) ;
214+ } ,
215+ methods : {
216+ handleReqFailure ( e ) {
217+ if ( e . response . status === 404 ) {
218+ this . aboutHtml =
219+ "<p>Failed to get configuration options for this version, please select the version from the dropdown above.</p>" ;
220+ } else if (
221+ e . response . status === 403 &&
222+ e . response . headers [ "X-RateLimit-Remaining" ] === 0
223+ ) {
224+ const resetDate = new Date (
225+ e . response . headers [ 'X-RateLimit-Reset' ] * 1000
226+ ) . toLocaleString ( ) ;
227+ this . aboutHtml =
228+ `<p>You have hit the GitHub API rate limit; documentation cannot be updated.` +
229+ `<p>The rate limit will be reset at ${ resetDate } .</p>` ;
230+ } else {
231+ this . aboutHtml =
232+ `<p>Ecountered an error when fetching documentation data:</p>` +
233+ `<pre><code>${ e . response . data } </code></pre>` +
234+ `<p>We would appreciate <a href="https://github.com/rust-lang/rustfmt/issues/new?template=bug_report.md">a bug report</a>.` +
235+ `<p>Try refreshing the page.</p>` ;
124236 }
125- } , 100 ) ;
237+ }
126238 }
127239 } ) ;
128240 const extractDepthOnes = ( ast ) => {
144256 const lastIndex = stack . length - 1 ;
145257 stack [ lastIndex ] . push ( next ) ;
146258 return stack ;
147- } ,
259+ } ,
148260 [ [ ] ] ) ;
149261 } ) ;
150262 }
155267 head : val [ 0 ] . text ,
156268 value : val ,
157269 stable : val . some ( ( elem ) => {
158- return ! ! elem . text && elem . text . includes ( "**Stable**: Yes" )
270+ return elem . type === "list" &&
271+ ! ! elem . raw &&
272+ elem . raw . includes ( "**Stable**: Yes" ) ;
159273 } ) ,
160274 text : val . reduce ( ( result , next ) => {
161275 return next . text != null
179293 configurationAbout , ...configurationDescriptions
180294 ] = configurations ;
181295 configurationAbout . value . links = { } ;
182-
296+
183297 return {
184298 about,
185299 configurationAbout : configurationAbout . value ,
186300 configurationDescriptions
187301 } ;
188302 }
303+ function htmlToId ( text ) {
304+ const tmpl = document . createElement ( 'template' ) ;
305+ tmpl . innerHTML = text . trim ( ) ;
306+ return encodeURIComponent ( CSS . escape ( tmpl . content . textContent ) ) ;
307+ }
189308 </ script >
190309 </ body >
191- </ html >
310+ </ html >
0 commit comments