3
3
< head >
4
4
< meta name ="viewport " content ="width=device-width ">
5
5
< 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 ">
6
7
< script src ="https://cdn.jsdelivr.net/npm/marked/marked.min.js "> </ script >
7
8
< 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 >
8
11
< script src ="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js "> </ script >
9
12
< style >
10
13
@media (max-width : 767px ) {
38
41
.searchCondition > div {
39
42
margin-right : 30px ;
40
43
}
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
+ }
41
53
</ style >
42
54
</ head >
43
55
< body >
59
71
< label for ="stable "> stable: </ label >
60
72
< input type ="checkbox " id ="stable " v-model ="shouldStable ">
61
73
</ 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 >
62
82
</ div >
63
83
< div v-html ="aboutHtml "> </ div >
64
84
< div v-html ="configurationAboutHtml "> </ div >
65
85
< div v-html ="outputHtml "> </ div >
66
86
</ article >
67
87
</ div >
68
88
< 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' ;
70
91
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' ;
71
102
new Vue ( {
72
103
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 ,
83
114
} ,
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
+
86
150
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
+ } , [ ] ) ;
102
163
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
+ } ) ;
104
187
}
105
188
} ,
106
189
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 ) ;
116
204
} ,
117
- mounted ( ) {
205
+ updated ( ) {
118
206
if ( UrlHash === '' ) return ;
119
- const interval = setInterval ( ( ) => {
207
+ this . $nextTick ( ( ) => {
120
208
const target = document . querySelector ( `#${ UrlHash } ` ) ;
121
- if ( target != null ) {
209
+ if ( target != null && ! this . scrolledOnce ) {
122
210
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>` ;
124
236
}
125
- } , 100 ) ;
237
+ }
126
238
}
127
239
} ) ;
128
240
const extractDepthOnes = ( ast ) => {
144
256
const lastIndex = stack . length - 1 ;
145
257
stack [ lastIndex ] . push ( next ) ;
146
258
return stack ;
147
- } ,
259
+ } ,
148
260
[ [ ] ] ) ;
149
261
} ) ;
150
262
}
155
267
head : val [ 0 ] . text ,
156
268
value : val ,
157
269
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" ) ;
159
273
} ) ,
160
274
text : val . reduce ( ( result , next ) => {
161
275
return next . text != null
179
293
configurationAbout , ...configurationDescriptions
180
294
] = configurations ;
181
295
configurationAbout . value . links = { } ;
182
-
296
+
183
297
return {
184
298
about,
185
299
configurationAbout : configurationAbout . value ,
186
300
configurationDescriptions
187
301
} ;
188
302
}
303
+ function htmlToId ( text ) {
304
+ const tmpl = document . createElement ( 'template' ) ;
305
+ tmpl . innerHTML = text . trim ( ) ;
306
+ return encodeURIComponent ( CSS . escape ( tmpl . content . textContent ) ) ;
307
+ }
189
308
</ script >
190
309
</ body >
191
- </ html >
310
+ </ html >
0 commit comments