1
- import { Octokit } from '@octokit/rest' ;
2
1
import Vue from 'vue' ;
3
2
4
- import VueSelect from 'vue-select' ;
3
+ import { hydrateAppWithData } from './hydration' ;
4
+ import { App } from './components'
5
5
6
- const octokit = new Octokit ( {
7
- auth : ''
8
- } ) ;
9
-
10
- const IssueCard = {
11
- template : `
12
- <div class="card entry-post vertical margin-top-normal padding-normal">
13
- <h4 class="card-title b-header margin-bottom-small">{{ issue.title }}</h4>
14
- <a :href="issue.html_url" class="button is-text tiny site-link" target="_blank">
15
- <span class="has-color-forest-green">
16
- {{ issue.repository.name }}#{{ issue.number }}
17
- </span>
18
- <i class="icon external-link has-color-forest-green"></i>
19
- </a>
20
- <div class="labels margin-top-small">
21
- <span
22
- v-for="labelName in issue.labelNames"
23
- class="button tag margin-right-small">
24
- {{ labelName }}
25
- </span>
26
- </div>
27
- </div>` ,
28
- props : {
29
- issue : {
30
- type : Object ,
31
- required : true
32
- } ,
33
- }
34
- }
35
-
36
- const App = {
37
- el : '#vue-app' ,
38
- template : `
39
- <div class="find-issues">
40
- <form id="filters">
41
- <label for="aspect">
42
- <strong>Aspect</strong><br>
43
- What aspect of the codebase would you like to work on?
44
- Leave blank if you don't have a preference.
45
- </label>
46
- <VueSelect
47
- v-model="filters.aspect"
48
- id="aspect"
49
- name="aspect"
50
- :options="options.aspects"
51
- label="name"
52
- :reduce="aspect => aspect.code"/>
53
-
54
- <label for="skills">
55
- <strong>Skill set</strong><br>
56
- Choose up to three skills that you would like to see issues for.
57
- Leave blank if you don't have a preference.
58
- </label>
59
- <VueSelect
60
- v-model="filters.skills"
61
- id="skills"
62
- name="skills"
63
- :options="options.skills"
64
- label="name"
65
- :reduce="skill => skill.code"
66
- :selectable="() => filters.skills.length < 3"
67
- multiple/>
68
-
69
- <label for="experience">
70
- <strong>Experience</strong><br>
71
- Are you a new contributor or do you have experience working with CC?
72
- </label>
73
- <VueSelect
74
- v-model="filters.experience"
75
- id="experience"
76
- name="experience"
77
- :options="options.experiences"
78
- label="name"
79
- :reduce="experience => experience.code"
80
- :clearable="false"/>
6
+ $ ( document ) . ready ( function ( ) {
7
+ const BASE_URL = 'https://raw.githubusercontent.com/creativecommons/ccos-scripts/master/normalize_repos'
8
+ const FILE_URL = name => `${ BASE_URL } /${ name } .json`
81
9
82
- <button
83
- class="button small is-success margin-top-small"
84
- @click.prevent="search">
85
- Search
86
- </button>
87
- </form>
88
-
89
- <template v-if="issues.length">
90
- <issue-card
91
- v-for="issue in filteredIssues"
92
- :key="issue.id"
93
- :issue="issue"/>
94
- </template>
95
- <p
96
- v-else
97
- class="margin-top-normal">
98
- No results.
99
- </p>
100
- </div>` ,
101
- components : {
102
- VueSelect,
103
- IssueCard
104
- } ,
105
- data ( ) {
106
- return {
107
- message : 'Hello' ,
108
- options : {
109
- skills : [
110
- {
111
- name : 'Python' ,
112
- code : 'python'
113
- } ,
114
- {
115
- name : 'JavaScript' ,
116
- code : 'javascript'
117
- } ,
118
- {
119
- name : 'Sass' ,
120
- code : 'sass'
121
- }
122
- ] ,
123
- experiences : [
124
- {
125
- name : 'New contributor' ,
126
- code : 'beginner'
127
- } ,
128
- {
129
- name : 'Experienced' ,
130
- code : 'experienced'
131
- }
132
- ] ,
133
- aspects : [
134
- {
135
- name : '📄 Text' ,
136
- code : '📄 aspect: text'
137
- } ,
138
- {
139
- name : '💻 Code' ,
140
- code : '💻 aspect: code'
141
- } ,
142
- {
143
- name : '🕹 Interface' ,
144
- code : '🕹 aspect: interface'
145
- } ,
146
- {
147
- name : '🤖 DX' ,
148
- code : '🤖 aspect: dx'
149
- }
150
- ]
151
- } ,
152
- filters : {
153
- aspect : null ,
154
- skills : [ ] ,
155
- experience : 'experienced'
156
- } ,
157
- issues : [ ]
158
- }
159
- } ,
160
- computed : {
161
- /**
162
- * Get a nested list of all the labels to search for. This is a combination
163
- * of the experience and aspect filter only. The skills filter must be
164
- * applied on the client side due to limitations of the GitHub API.
165
- *
166
- * This returns a list of lists. Each nested list corresponds to a single
167
- * API query and multiple queries need to be run to get the combined set of
168
- * valid issues.
169
- *
170
- * @returns {array } - the array of array of labels
171
- */
172
- labelsList ( ) {
173
- const labelsList = [
174
- [ 'good first issue' ]
175
- ]
176
- if ( this . filters . experience === 'experienced' ) {
177
- labelsList . push ( [ 'help wanted' ] )
178
- }
179
- if ( this . filters . aspect ) {
180
- labelsList . forEach ( labels => {
181
- labels . push ( this . filters . aspect )
182
- } )
183
- }
184
- return labelsList
185
- } ,
186
- /**
187
- * Get all the promises which will yield the issues being searched for.
188
- *
189
- * @returns {array } - the array of promises that will resolve to issues
190
- */
191
- promises ( ) {
192
- return this . labelsList . map ( labels => this . promise ( labels ) )
193
- } ,
194
- /**
195
- * Remove duplicates from the list of issues. Since two different API
196
- * queries are being collated, some issues may appear more than once. This
197
- * action removes such duplicates from the union of both results.
198
- *
199
- * @returns {array } - the array of issues without any duplicate entries
200
- */
201
- dedupedIssues ( ) {
202
- const ids = new Set ( )
203
- const deduped = [ ]
204
- this . issues . forEach ( issue => {
205
- if ( ids . has ( issue . id ) ) {
206
- return
207
- }
208
- deduped . push ( issue )
209
- ids . add ( issue . id )
210
- } )
211
- return deduped
212
- } ,
213
- filteredIssues ( ) {
214
- let filtered = this . dedupedIssues
215
- if ( this . filters . skills . length ) {
216
- filtered = filtered . filter ( issue => {
217
- const joinedLabels = issue . labelNames . join ( ',' )
218
- return this . filters . skills . some ( skill => joinedLabels . includes ( skill ) )
219
- } )
220
- }
221
- return filtered
222
- }
223
- } ,
224
- methods : {
225
- /**
226
- * Get a Promise for the list of issues pertaining to a given skill.
227
- *
228
- * @param {Array } labels - list of labels to search for
229
- * @returns {Promise } a promise that resolves into a list of issues
230
- */
231
- promise ( labels = [ ] ) {
232
- const params = {
233
- org : 'creativecommons' ,
234
- state : 'open' ,
235
- filter : 'all'
236
- }
237
- if ( labels ) {
238
- params . labels = labels . join ( ',' )
239
- }
240
- return octokit
241
- . issues
242
- . listForOrg ( params )
243
- . then ( response => response . data )
244
- } ,
245
- /**
246
- * Run the search based on the data submitted via the form and load all
247
- * results into the `issues` attribute.
248
- */
249
- search ( ) {
250
- Promise
251
- . all ( this . promises )
252
- . then ( issueLists => {
253
- const issues = issueLists . flat ( )
254
- issues . forEach ( issue => {
255
- issue . labelNames = issue . labels . map ( label => label . name )
10
+ Promise
11
+ . all ( [
12
+ fetch ( FILE_URL ( 'skills' ) )
13
+ . then ( response => response . json ( ) )
14
+ . then ( data => {
15
+ window . skills = data
16
+ } ) ,
17
+ fetch ( FILE_URL ( 'labels' ) )
18
+ . then ( response => response . json ( ) )
19
+ . then ( data => {
20
+ window . labels = data
256
21
} )
257
- this . issues = issues
258
- } )
259
- . catch ( err => console . error ( err ) )
260
- }
261
- } ,
262
- mounted ( ) {
263
- this . search ( )
264
- }
265
- }
266
-
267
- $ ( document ) . ready ( function ( ) {
268
- window . app = new Vue ( App )
269
- } )
22
+ ] )
23
+ . then ( ( ) => {
24
+ hydrateAppWithData ( )
25
+ window . app = new Vue ( App )
26
+ } )
27
+ . catch ( err => console . error ( err ) )
28
+ } )
0 commit comments